├── .github └── workflows │ └── test.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── META6.json ├── README.chromedriver.md ├── README.md ├── examples ├── blackberry.rakutest ├── chrome.rakutest ├── firefox.rakutest ├── phantomjs-multiple.rakutest └── phantomjs.rakutest ├── lib └── Selenium │ ├── WebDriver.rakumod │ └── WebDriver │ ├── BlackBerry.rakumod │ ├── Chrome.rakumod │ ├── Edge.rakumod │ ├── Firefox.rakumod │ ├── InternetExplorer.rakumod │ ├── Keys.rakumod │ ├── Opera.rakumod │ ├── PhantomJS.rakumod │ ├── Safari.rakumod │ ├── WebElement.rakumod │ ├── WebWindow.rakumod │ ├── Wire.rakumod │ └── X │ └── Error.rakumod ├── logotype └── logo_32x32.png ├── resources └── firefox_extension │ ├── prefs.json │ └── webdriver.xpi └── t ├── 01-load.rakutest ├── 02-phantomjs.rakutest ├── 03-webelement.rakutest ├── 04-webwindow.rakutest ├── 05-firefox.rakutest ├── 06-chrome.rakutest ├── 07-blackberry.rakutest └── 99-author-meta.rakutest /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macOS-latest 18 | - windows-latest 19 | raku-version: 20 | - 'latest' 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: Raku/setup-raku@v1 25 | with: 26 | raku-version: ${{ matrix.raku-version }} 27 | - name: Install Dependencies 28 | run: zef install --/test --test-depends --deps-only . 29 | - name: Install App::Prove6 30 | run: zef install --/test App::Prove6 31 | - name: Run Tests 32 | run: prove6 -l t 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .precomp 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: perl6 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | perl6: 10 | - 2016.11 11 | - latest 12 | 13 | install: 14 | - echo $TRAVIS_OS_NAME 15 | - rakudobrew build zef 16 | - zef --depsonly install . 17 | - zef install Test::META 18 | - zef build . 19 | 20 | before_script: 21 | # Start xvfb for Firefox headless testing and give it some time to start 22 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then export DISPLAY=:99.0 ; fi 23 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sh -e /etc/init.d/xvfb start ; fi 24 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sleep 3 ; fi 25 | # Install PhantomJS on MacOSX using homebrew 26 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi 27 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install phantomjs ; fi 28 | # Show PhantomJS version 29 | - phantomjs --version 30 | 31 | script: 32 | - AUTHOR_TESTING=1 prove -ve "perl6 -Ilib" t 33 | - zef install . 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ahmad M. Zawawi 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 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "perl": "6.c", 3 | "name": "Selenium::WebDriver", 4 | "license" : "MIT", 5 | "version": "0.0.2", 6 | "description": "Raku Bindings for Selenium WebDriver", 7 | "depends": [ 8 | "File::Temp", 9 | "File::Which", 10 | "File::Zip", 11 | "HTTP::UserAgent", 12 | "JSON::Tiny", 13 | "MIME::Base64", 14 | "URI::Encode" 15 | ], 16 | "test-depends": [ 17 | "Test" 18 | ], 19 | "resources": [ 20 | "firefox_extension/prefs.json", 21 | "firefox_extension/webdriver.xpi" 22 | ], 23 | "provides": { 24 | "Selenium::WebDriver": "lib/Selenium/WebDriver.rakumod", 25 | "Selenium::WebDriver::BlackBerry": "lib/Selenium/WebDriver/BlackBerry.rakumod", 26 | "Selenium::WebDriver::Chrome": "lib/Selenium/WebDriver/Chrome.rakumod", 27 | "Selenium::WebDriver::Edge": "lib/Selenium/WebDriver/Edge.rakumod", 28 | "Selenium::WebDriver::Firefox": "lib/Selenium/WebDriver/Firefox.rakumod", 29 | "Selenium::WebDriver::InternetExplorer": "lib/Selenium/WebDriver/InternetExplorer.rakumod", 30 | "Selenium::WebDriver::Keys": "lib/Selenium/WebDriver/Keys.rakumod", 31 | "Selenium::WebDriver::Opera": "lib/Selenium/WebDriver/Opera.rakumod", 32 | "Selenium::WebDriver::PhantomJS": "lib/Selenium/WebDriver/PhantomJS.rakumod", 33 | "Selenium::WebDriver::Safari": "lib/Selenium/WebDriver/Safari.rakumod", 34 | "Selenium::WebDriver::WebElement": "lib/Selenium/WebDriver/WebElement.rakumod", 35 | "Selenium::WebDriver::WebWindow": "lib/Selenium/WebDriver/WebWindow.rakumod", 36 | "Selenium::WebDriver::Wire": "lib/Selenium/WebDriver/Wire.rakumod", 37 | "Selenium::WebDriver::X::Error": "lib/Selenium/WebDriver/X/Error.rakumod" 38 | }, 39 | "source-url": "git://github.com/azawawi/raku-selenium-webdriver.git" 40 | } 41 | -------------------------------------------------------------------------------- /README.chromedriver.md: -------------------------------------------------------------------------------- 1 | == Short setup guide for installing the chromedriver == 2 | 3 | = Debian based systems = 4 | 5 | Install the Chromium browser as well as the chromedriver: 6 | 7 | ``` 8 | $ sudo apt-get install chromium-browser chromium-chromedriver 9 | ``` 10 | 11 | Please remember to add `/usr/lib/chromium-browser/ to your PATH 12 | 13 | ``` 14 | $ export PATH=$PATH:/usr/lib/chromium-browser/ 15 | ``` 16 | 17 | Then check that it is working as expected: 18 | 19 | ``` 20 | $ chromedriver --version 21 | ChromeDriver 2.24 22 | ``` 23 | 24 | If you get the following error message instead: 25 | 26 | ``` 27 | "chromedriver: error while loading shared libraries: libui_base.so: cannot open shared object file: No such file or directory" 28 | ``` 29 | 30 | Then add the library path of the chrome browser: 31 | 32 | ``` 33 | $ sudo sh -c 'echo "/usr/lib/chromium-browser/libs" > /etc/ld.so.conf.d/chrome_lib.conf' 34 | ``` 35 | 36 | ``` 37 | $ sudo ldconfig 38 | ``` 39 | 40 | And check again: 41 | 42 | ``` 43 | $ chromedriver --version 44 | ChromeDriver 2.24 45 | ``` 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Selenium::WebDriver 2 | [![Actions 3 | Status](https://github.com/azawawi/raku-selenium-webdriver/workflows/test/badge.svg)](https://github.com/azawawi/raku-selenium-webdriver/actions) 4 | 5 | This module provides the [Raku](http://raku.org) bindings for the [Selenium WebDriver Wire Protocol](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol). 6 | 7 | ***Note:*** This module is a work in progress. Please see its project status [here](https://github.com/azawawi/perl6-selenium-webdriver/blob/master/README.md#project-status). 8 | 9 | ## Example 10 | 11 | ```Raku 12 | use v6; 13 | use Selenium::WebDriver::PhantomJS; 14 | 15 | my $driver = Selenium::WebDriver::PhantomJS.new; 16 | $driver.url("http://google.com"); 17 | say "Title: " ~ $driver.title; 18 | say "URL: " ~ $driver.url; 19 | say "Source length: " ~ $driver.source.chars; 20 | $driver.save-screenshot('test.png'); 21 | LEAVE { 22 | $driver.quit if $driver.defined 23 | }; 24 | ``` 25 | 26 | For more examples, please see the [examples](examples) folder. 27 | 28 | ## PhantomJS Installation 29 | 30 | ### Linux/Debian 31 | 32 | To install phantomjs on Debian, please type the following: 33 | ``` 34 | $ sudo apt-get install phantomjs 35 | ``` 36 | 37 | **CAUTION**: Also there are [prebuilt binaries]( 38 | https://bitbucket.org/ariya/phantomjs/downloads) for PhantomJS for Linux if the packaged version is a bit old. 39 | 40 | ### Mac OS X 41 | 42 | To install PhantomJS on Mac OS X, the simplest solution is to use brew: 43 | ``` 44 | $ brew update 45 | $ brew install phantomjs 46 | ``` 47 | 48 | ### Windows 49 | 50 | To install PhantomJS on windows, please download a copy from 51 | [Here](http://phantomjs.org/) and then make it available in your PATH environment 52 | variable. 53 | 54 | ### Travis CI 55 | 56 | Travis CI comes with [pre-installed PhantomJS]( 57 | http://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-PhantomJS). 58 | No special instructions are needed. 59 | 60 | ## Project Status 61 | 62 | | Web Driver | Status | 63 | | ------------- | ------------- | 64 | | PhantomJS | **DONE** but needs more tests | 65 | | Firefox | **DONE** | 66 | | Chrome | **DONE** but needs external [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) | 67 | | Safari | Pending | 68 | | Opera | Pending | 69 | | MSIE | Pending | 70 | | MSEdge | Pending | 71 | | BlackBerry | **DONE** | 72 | 73 | ## Installation 74 | 75 | To install it using zef (a module management tool bundled with Rakudo Star): 76 | 77 | ``` 78 | $ zef install Selenium::WebDriver 79 | ``` 80 | 81 | ## Testing 82 | 83 | - To run tests: 84 | ``` 85 | $ prove --ext .rakutest -ve "raku -I." 86 | ``` 87 | 88 | - To run all tests including author tests (Please make sure 89 | [Test::Meta](https://github.com/jonathanstowe/Test-META) is installed): 90 | ``` 91 | $ zef install Test::META 92 | $ TEST_AUTHOR=1 prove --ext .rakutest -ve "raku -I." 93 | ``` 94 | 95 | ## Author 96 | 97 | Ahmad M. Zawawi, [azawawi](https://github.com/azawawi/) on #raku 98 | 99 | ## License 100 | 101 | MIT License 102 | -------------------------------------------------------------------------------- /examples/blackberry.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use v6; 4 | 5 | use Test; 6 | plan 5; 7 | use Selenium::WebDriver::BlackBerry; 8 | 9 | # Create new BlackBerry webdriver. The ip address is shown in the 10 | # development section of the settings menu of the BlackBerry browser. 11 | my $driver = Selenium::WebDriver::BlackBerry.new('169.254.0.1'); 12 | 13 | # Navigate to google.com 14 | $driver.url( "http://google.com" ); 15 | ok $driver.title ~~ / 'Google' /, "Google in title"; 16 | ok $driver.url ~~ / ^ 'http://' .+? 'google'/, "google.com in url"; 17 | 18 | # Find search box and then type "Perl 6" in it 19 | my $search-box = $driver.element-by-name( 'q' ); 20 | $search-box.send-keys( "Perl 6" ); 21 | 22 | ok $search-box.tag-name.lc eq 'input', "Search box must be an "; 23 | ok $search-box.enabled, "Search box is enabled"; 24 | 25 | # Submit form 26 | $search-box.submit; 27 | 28 | # Take a screenshot 29 | my $filename = 'output.png'; 30 | $driver.save-screenshot( $filename ); 31 | ok $filename.IO ~~ :e, "$filename exists"; 32 | -------------------------------------------------------------------------------- /examples/chrome.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use v6; 4 | 5 | use Test; 6 | plan 5; 7 | use Selenium::WebDriver::Chrome; 8 | 9 | # Create new Chrome webdriver. Please note chromedriver must be already 10 | # installed and configured in PATH 11 | my $driver = Selenium::WebDriver::Chrome.new; 12 | 13 | # Navigate to google.com 14 | $driver.url( "http://google.com" ); 15 | ok $driver.title ~~ / 'Google' /, "Google in title"; 16 | ok $driver.url ~~ / ^ 'http://' .+? 'google'/, "google.com in url"; 17 | 18 | # Find search box and then type "Perl 6" in it 19 | my $search-box = $driver.element-by-name( 'q' ); 20 | $search-box.send-keys( "Perl 6" ); 21 | 22 | ok $search-box.tag-name.lc eq 'input', "Search box must be an "; 23 | ok $search-box.enabled, "Search box is enabled"; 24 | 25 | # Submit form 26 | $search-box.submit; 27 | 28 | # Take a screenshot 29 | my $filename = 'output.png'; 30 | $driver.save-screenshot( $filename ); 31 | ok $filename.IO ~~ :e, "$filename exists"; 32 | 33 | LEAVE { 34 | diag 'WebDriver cleanup'; 35 | $driver.quit if $driver.defined; 36 | } 37 | -------------------------------------------------------------------------------- /examples/firefox.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use v6; 4 | 5 | use Test; 6 | plan 5; 7 | use Selenium::WebDriver::Firefox; 8 | 9 | # Create new firefox webdriver. Please note firefox must be already 10 | # installed and configured in PATH 11 | my $driver = Selenium::WebDriver::Firefox.new; 12 | 13 | # Navigate to google.com 14 | $driver.url( "http://google.com" ); 15 | ok $driver.title ~~ / 'Google' /, "Google in title"; 16 | ok $driver.url ~~ / ^ 'http' 's'? '://' .+? 'google'/, "google.com in url"; 17 | 18 | # Find search box and then type "Perl 6" in it 19 | my $search-box = $driver.element-by-name( 'q' ); 20 | $search-box.send-keys( "Perl 6" ); 21 | 22 | ok $search-box.tag-name.lc eq 'input', "Search box must be an "; 23 | ok $search-box.enabled, "Search box is enabled"; 24 | 25 | # Submit form 26 | $search-box.submit; 27 | 28 | # Take a screenshot 29 | my $filename = 'output.png'; 30 | $driver.save-screenshot( $filename ); 31 | ok $filename.IO ~~ :e, "$filename exists"; 32 | 33 | LEAVE { 34 | diag 'WebDriver cleanup'; 35 | $driver.quit if $driver.defined; 36 | } 37 | -------------------------------------------------------------------------------- /examples/phantomjs-multiple.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use v6; 4 | 5 | use Test; 6 | use Selenium::WebDriver::PhantomJS; 7 | 8 | # Number of simulatenous drivers to test port collision 9 | constant NUM_OF_DRIVERS = 5; 10 | plan 3 * NUM_OF_DRIVERS; 11 | 12 | # Create two phantomjs webdriver 13 | # Please note phantomjs must be already installed 14 | my @drivers; 15 | for 1..NUM_OF_DRIVERS { 16 | diag "Driver #$_ starting up"; 17 | my $driver = Selenium::WebDriver::PhantomJS.new; 18 | ok($driver.defined, "WebDriver is defined"); 19 | 20 | @drivers.push($driver); 21 | 22 | # Navigate to google.com 23 | $driver.url( "http://google.com" ); 24 | 25 | ok($driver.title ~~ / 'Google' /, "Google in title"); 26 | ok($driver.url ~~ / ^ 'http://' .+? 'google'/, "google.com in url"); 27 | } 28 | 29 | LEAVE { 30 | diag 'WebDriver(s) cleanup'; 31 | $_.quit for @drivers; 32 | } 33 | -------------------------------------------------------------------------------- /examples/phantomjs.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use v6; 4 | 5 | use Test; 6 | plan 5; 7 | use Selenium::WebDriver::PhantomJS; 8 | 9 | # Create new phantomjs webdriver. Please note phantomjs must be already 10 | # installed and configured in PATH 11 | my $driver = Selenium::WebDriver::PhantomJS.new; 12 | 13 | # Navigate to google.com 14 | $driver.url( "http://google.com" ); 15 | ok $driver.title ~~ / 'Google' /, "Google in title"; 16 | ok $driver.url ~~ / ^ 'http://' .+? 'google'/, "google.com in url"; 17 | 18 | # Find search box and then type "Perl 6" in it 19 | my $search-box = $driver.element-by-name( 'q' ); 20 | $search-box.send-keys( "Perl 6" ); 21 | 22 | ok $search-box.tag-name.lc eq 'input', "Search box must be an "; 23 | ok $search-box.enabled, "Search box is enabled"; 24 | 25 | # Submit form 26 | $search-box.submit; 27 | 28 | # Take a screenshot 29 | my $filename = 'output.png'; 30 | $driver.save-screenshot( $filename ); 31 | ok $filename.IO ~~ :e, "$filename exists"; 32 | 33 | LEAVE { 34 | diag 'WebDriver cleanup'; 35 | $driver.quit if $driver.defined; 36 | } 37 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | use Selenium::WebDriver::PhantomJS; 5 | # use Selenium::WebDriver::Edge; 6 | use Selenium::WebDriver::Firefox; 7 | use Selenium::WebDriver::Chrome; 8 | # use Selenium::WebDriver::InternetExplorer; 9 | 10 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/BlackBerry.rakumod: -------------------------------------------------------------------------------- 1 | use v6; 2 | use Selenium::WebDriver::Wire; 3 | 4 | unit class Selenium::WebDriver::BlackBerry is Selenium::WebDriver::Wire; 5 | 6 | method new( 7 | Str $host, 8 | Int :$port = 1338, 9 | Bool :$debug = False ) 10 | { 11 | self.bless: :$host, :$port, :$debug 12 | } 13 | 14 | method start { 15 | say "Launching BlackBerry Driver on $.host():$.port()" if self.debug; 16 | } 17 | 18 | method stop { 19 | } 20 | 21 | # GET /sessions 22 | method sessions { 23 | my $result = self._execute-command( "GET", "/sessions" ); 24 | 25 | return unless $result.defined; 26 | return $result; 27 | } 28 | 29 | # We just pick the first active session here, or create a new one. 30 | method _new-session { 31 | my $sessions = self.sessions; 32 | if $sessions -> [$s] { 33 | return { sessionId => ~$s } 34 | } 35 | 36 | return self._execute-command( 37 | "POST", 38 | "/session", 39 | { 40 | "desiredCapabilities" => {}, 41 | "requiredCapabilities" => {}, 42 | } 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Chrome.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | use File::Which; 5 | use Selenium::WebDriver::Wire; 6 | 7 | unit class Selenium::WebDriver::Chrome is Selenium::WebDriver::Wire; 8 | 9 | has Proc::Async $.process is rw; 10 | 11 | method start { 12 | say "Launching Chrome Driver" if self.debug; 13 | 14 | # Find process in PATH 15 | my $chrome-driver = which("chromedriver"); 16 | die "Cannot find chromedriver in your PATH" unless $chrome-driver.defined; 17 | 18 | # Run it 19 | say "Chrome found at '$chrome-driver'" if self.debug; 20 | my $p = Proc::Async.new($chrome-driver, "--port=" ~ self.port); 21 | $p.start; 22 | 23 | self.process = $p; 24 | } 25 | 26 | method stop { 27 | self.process.kill if self.process.defined; 28 | } 29 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Edge.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | !!! 5 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Firefox.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | use Selenium::WebDriver::Wire; 5 | use File::Which; 6 | use File::Temp; 7 | use File::Zip; 8 | use JSON::Tiny; 9 | 10 | unit class Selenium::WebDriver::Firefox is Selenium::WebDriver::Wire; 11 | 12 | has Proc::Async $.process is rw; 13 | 14 | method start { 15 | # firefox webdriver extension needs this weird url prefix 16 | self.url-prefix = "/hub"; 17 | 18 | # Find webdriver extension resources in our module installed location 19 | say 'Finding webdriver location using %?RESOURCES' if self.debug; 20 | my $webdriver-xpi = %?RESOURCES{'firefox_extension/webdriver.xpi'}; 21 | fail "Cannot find webdriver.xpi" unless $webdriver-xpi.defined; 22 | my $firefox-prefs = %?RESOURCES{'firefox_extension/prefs.json'}; 23 | fail "Cannot find prefs.json" unless $firefox-prefs.defined; 24 | 25 | # Create a temporary folder for our temporary firefox profile 26 | my ($directory, $dirhandle) = tempdir; 27 | 28 | # unzip webdriver.xpi 29 | my $profile-path = "$directory/perl6-selenium-webdriver"; 30 | my $extension-path = "$profile-path/extensions/fxdriver@googlecode.com"; 31 | my $prefs-file-name = "$profile-path/user.js"; 32 | 33 | # Read firefox json-formatted preferences 34 | my $prefs = from-json($firefox-prefs.IO.slurp); 35 | 36 | # Create temporary profile path 37 | $profile-path.IO.mkdir; 38 | 39 | # Modify mutable port that were loaded from prefs.json 40 | $prefs = self.port; 41 | 42 | # Write a user.js file in profile path 43 | my $fh = $prefs-file-name.IO.open(:w); 44 | for $prefs.kv -> $k, $v { 45 | my $value = to-json($v); 46 | $fh.say(qq{user_pref("$k", $value);}); 47 | } 48 | for $prefs.kv -> $k, $v { 49 | my $value = to-json($v); 50 | $fh.say(qq{user_pref("$k", $value);}); 51 | } 52 | $fh.close; 53 | 54 | $extension-path.IO.mkdir; 55 | 56 | # Unzip the webdriver extension (XPI file format is simply a ZIP archive) 57 | say "unzipping $webdriver-xpi into $extension-path"; 58 | my $zip-file = File::Zip.new(file-name => ~$webdriver-xpi); 59 | $zip-file.unzip(directory => $extension-path); 60 | 61 | # Setup firefox environment 62 | # %*ENV = "firefox.log"; 63 | %*ENV = $profile-path; 64 | %*ENV = "1"; 65 | %*ENV = "1"; 66 | %*ENV = "1"; 67 | 68 | say "Launching firefox" if self.debug; 69 | 70 | # Find firefox process in PATH 71 | my $firefox = which("firefox"); 72 | die "Cannot find firefox in your PATH" unless $firefox.defined; 73 | 74 | # Run it asynchrously 75 | say "Firefox found at '$firefox'" if self.debug; 76 | my $p = Proc::Async.new($firefox); 77 | $p.start; 78 | 79 | # And store the process to be able to kill it when we're done 80 | self.process = $p; 81 | } 82 | 83 | method stop { 84 | self.process.kill if self.process.defined; 85 | } 86 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/InternetExplorer.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | !!! 5 | 6 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Keys.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | unit module Selenium::WebDriver::Keys; 5 | 6 | our constant %Keys is export = { 7 | 'NULL' => "\x000", 8 | 'CANCEL' => "\x001", 9 | 'HELP' => "\x002", 10 | 'BACK-SPACE' => "\x003", 11 | 'TAB' => "\x004", 12 | 'CLEAR' => "\x005", 13 | 'RETURN' => "\x006", 14 | 'ENTER' => "\x007", 15 | 'SHIFT' => "\x008", 16 | 'CONTROL' => "\x009", 17 | 'ALT' => "\x00A", 18 | 'PAUSE' => "\x00B", 19 | 'ESCAPE' => "\x00C", 20 | 'SPACE' => "\x00D", 21 | 'PAGE-UP' => "\x00E", 22 | 'PAGE-DOWN' => "\x00F", 23 | 'END' => "\x010", 24 | 'HOME' => "\x011", 25 | 'LEFT-ARROW' => "\x012", 26 | 'UP-ARROW' => "\x013", 27 | 'RIGHT-ARROW' => "\x014", 28 | 'DOWN-ARROW' => "\x015", 29 | 'INSERT' => "\x016", 30 | 'DELETE' => "\x017", 31 | 'SEMICOLON' => "\x018", 32 | 'EQUALS' => "\x019", 33 | 'NUMPAD0' => "\x01A", 34 | 'NUMPAD1' => "\x01B", 35 | 'NUMPAD2' => "\x01C", 36 | 'NUMPAD3' => "\x01D", 37 | 'NUMPAD4' => "\x01E", 38 | 'NUMPAD5' => "\x01F", 39 | 'NUMPAD6' => "\x020", 40 | 'NUMPAD7' => "\x021", 41 | 'NUMPAD8' => "\x022", 42 | 'NUMPAD9' => "\x023", 43 | 'MULTIPLY' => "\x024", 44 | 'ADD' => "\x025", 45 | 'SEPARATOR' => "\x026", 46 | 'SUBTRACT' => "\x027", 47 | 'DECIMAL' => "\x028", 48 | 'DIVIDE' => "\x029", 49 | 'F1' => "\x031", 50 | 'F2' => "\x032", 51 | 'F3' => "\x033", 52 | 'F4' => "\x034", 53 | 'F5' => "\x035", 54 | 'F6' => "\x036", 55 | 'F7' => "\x037", 56 | 'F8' => "\x038", 57 | 'F9' => "\x039", 58 | 'F10' => "\x03A", 59 | 'F11' => "\x03B", 60 | 'F12' => "\x03C", 61 | 'COMMAND-META' => "\x03D", 62 | }; 63 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Opera.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | !!! 5 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/PhantomJS.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | use Selenium::WebDriver::Wire; 5 | use File::Which; 6 | 7 | unit class Selenium::WebDriver::PhantomJS is Selenium::WebDriver::Wire; 8 | 9 | has Proc::Async $.process is rw; 10 | 11 | method start { 12 | say "Starting phantomjs process" if $.debug; 13 | 14 | # Find process in PATH 15 | my $path = which( 'phantomjs' ); 16 | die "Cannot find phantomjs in your PATH" unless $path.defined; 17 | 18 | say "phantomjs path: $path" if $.debug; 19 | say "phantomjs port: $.port" if $.debug; 20 | my $process = Proc::Async.new( 21 | $path, 22 | "--webdriver=" ~ $.port, 23 | "--webdriver-loglevel=" ~ ($.debug ?? "DEBUG" !! "WARN"), 24 | ); 25 | my $p = $process.start; 26 | say("phantomjs returned Proc::Async promise: " ~ $p.perl) if $.debug; 27 | 28 | self.process = $process; 29 | } 30 | 31 | method stop { 32 | self.process.kill if self.process.defined; 33 | } 34 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Safari.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | !!! 5 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/WebElement.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | unit class Selenium::WebDriver::WebElement; 5 | 6 | has Str $.id is rw; 7 | has $.driver is rw; 8 | 9 | # POST /session/:sessionId/element/:id/click 10 | method click { 11 | return $.driver._post( "element/$.id/click" ); 12 | } 13 | 14 | # POST /session/:sessionId/element/:id/value 15 | method send-keys(Str $keys) { 16 | return $.driver._post( 17 | "element/$.id/value", 18 | { 19 | "value" => $keys.comb; 20 | } 21 | ); 22 | } 23 | 24 | # GET /session/:sessionId/element/:id/name 25 | method tag-name { 26 | return $.driver._get( "element/$.id/name" ); 27 | } 28 | 29 | # GET /session/:sessionId/element/:id/selected 30 | method selected { 31 | return $.driver._get( "element/$.id/selected" ); 32 | } 33 | 34 | # GET /session/:sessionId/element/:id/enabled 35 | method enabled { 36 | return $.driver._get( "element/$.id/enabled" ); 37 | } 38 | 39 | # GET /session/:sessionId/element/:id/attribute/:name 40 | method attr(Str $name) { 41 | return $.driver._get( "element/$.id/attribute/$name" ); 42 | } 43 | 44 | # GET /session/:sessionId/element/:id/equals/:other 45 | method equals-by-id(Str $id) { 46 | return $.driver._get( "element/$.id/equals/$id" ); 47 | } 48 | 49 | # GET /session/:sessionId/element/:id/displayed 50 | method displayed { 51 | return $.driver._get( "element/$.id/displayed" ); 52 | } 53 | 54 | # GET /session/:sessionId/element/:id/location 55 | method location { 56 | return $.driver._get( "element/$.id/location" ); 57 | } 58 | 59 | # GET /session/:sessionId/element/:id/location_in_view 60 | method location-in-view { 61 | return $.driver._get( "element/$.id/location_in_view" ); 62 | } 63 | 64 | # GET /session/:sessionId/element/:id/size 65 | method size(Str $id) { 66 | return $.driver._get( "element/$.id/size" ); 67 | } 68 | 69 | # GET /session/:sessionId/element/:id/css/:propertyName 70 | method css(Str $property-name) { 71 | return $.driver._get( "element/$.id/css/$property-name"); 72 | } 73 | 74 | # POST /session/:sessionId/element/:id/submit 75 | method submit { 76 | return $.driver._post( "element/$.id/submit" ); 77 | } 78 | 79 | # GET /session/:sessionId/element/:id/text 80 | method text { 81 | return $.driver._get( "element/$.id/text" ); 82 | } 83 | 84 | method clear { 85 | return $.driver._post( "element/$.id/clear" ); 86 | } 87 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/WebWindow.rakumod: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | unit class Selenium::WebDriver::WebWindow; 4 | 5 | has Str $.handle is rw; 6 | has $.driver is rw; 7 | 8 | # POST /session/:sessionId/window 9 | method current(Str $name) { 10 | return $.driver._post( 'window', { name => $name } ); 11 | } 12 | 13 | # DELETE /session/:sessionId/window 14 | method close-current { 15 | return $.driver._delete( 'window' ); 16 | } 17 | 18 | # POST /session/:sessionId/window/:windowHandle/size 19 | multi method size(Int $width, Int $height) { 20 | return $.driver._post( 21 | "window/$.handle/size", 22 | { width => $width, height => $height } 23 | ); 24 | } 25 | 26 | # GET /session/:sessionId/window/:windowHandle/size 27 | multi method size returns Hash { 28 | return $.driver._get( "window/$.handle/size" ); 29 | } 30 | 31 | # POST /session/:sessionId/window/:windowHandle/position 32 | multi method position(Int $x, Int $y) returns Hash { 33 | return $.driver._post( "window/$.handle/position", { x => $x, y => $y } ); 34 | } 35 | 36 | # GET /session/:sessionId/window/:windowHandle/position 37 | multi method position returns Hash { 38 | return $.driver._get( "window/$.handle/position" ); 39 | } 40 | 41 | # POST /session/:sessionId/window/:windowHandle/maximize 42 | multi method maximize { 43 | return $.driver._post( "window/$.handle/maximize" ); 44 | } 45 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/Wire.rakumod: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # JSON Wire Protocol Perl 6 implementation 4 | # Please see 5 | # https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol 6 | # 7 | 8 | use v6; 9 | 10 | =begin markdown 11 | =end markdown 12 | unit class Selenium::WebDriver::Wire; 13 | 14 | use HTTP::UserAgent; 15 | use JSON::Tiny; 16 | use MIME::Base64; 17 | use URI::Encode; 18 | use Selenium::WebDriver::WebElement; 19 | use Selenium::WebDriver::WebWindow; 20 | use Selenium::WebDriver::X::Error; 21 | 22 | has Bool $.debug is rw; 23 | has Str $.host is rw; 24 | has Int $.port is rw; 25 | has Str $.url-prefix is rw; 26 | has Str $.session-id is rw; 27 | 28 | =begin markdown 29 | =end markdown 30 | submethod BUILD( 31 | Str :$host = '127.0.0.1', 32 | Int :$port = -1, 33 | Str :$url-prefix = '', 34 | Bool :$debug = False, 35 | Int :$max-attempts where $_ >= 1 = 10 ) 36 | { 37 | 38 | # Attributes 39 | self.debug = $debug; 40 | self.host = $host; 41 | self.url-prefix = $url-prefix; 42 | 43 | # We need to find an empty port given that no port was given 44 | self.port = $port == -1 ?? self._empty-port !! $port; 45 | 46 | # Start behavior (normally implemented in children) 47 | self.start; 48 | 49 | # Try to create a session for n times 50 | my $session; 51 | for 1..$max-attempts { 52 | # Try to create session 53 | $session = self._new-session; 54 | last if $session.defined; 55 | 56 | CATCH { 57 | default { 58 | # Retry session creation failure after timeout 59 | say "Attempt $_ to create session" if self.debug; 60 | sleep 1; 61 | } 62 | } 63 | } 64 | 65 | # No session could be created 66 | die "Cannot obtain a session after $max-attempts attempts" unless $session.defined; 67 | 68 | self.session-id = $session; 69 | die "Session id is not defined" unless self.session-id.defined; 70 | } 71 | 72 | # Normally implemented in subclasses 73 | method start { } 74 | 75 | # Normally implemented in subclasses 76 | method stop { } 77 | 78 | =begin markdown 79 | =end markdown 80 | # POST /session 81 | method _new-session { 82 | return self._execute-command( 83 | "POST", 84 | "/session", 85 | { 86 | "desiredCapabilities" => {}, 87 | "requiredCapabilities" => {}, 88 | } 89 | ); 90 | } 91 | 92 | =begin markdown 93 | =end markdown 94 | # GET /sessions 95 | method sessions { 96 | my $result = self._execute-command( "GET", "/sessions" ); 97 | 98 | return unless $result.defined; 99 | return $result; 100 | } 101 | 102 | # GET /session/:sessionId 103 | method capabilities { 104 | return self._execute-command( "GET", "/session/$(self.session-id)" ); 105 | } 106 | 107 | # DELETE /session/:sessionId 108 | method _delete_session { 109 | return self._execute-command( "DELETE", "/session/$(self.session-id)" ); 110 | } 111 | 112 | # POST /session/:sessionId/timeouts 113 | method _timeouts(Str $type, Int $timeout_millis) { 114 | return self._post( 115 | 'timeouts', 116 | { 117 | type => $type, 118 | ms => $timeout_millis 119 | } 120 | ); 121 | } 122 | 123 | method script-timeout(Int $timeout_millis) { 124 | return self._timeouts( 'script', $timeout_millis ); 125 | } 126 | 127 | method implicit-timeout(Int $timeout_millis) { 128 | return self._timeouts( 'implicit', $timeout_millis ); 129 | } 130 | 131 | method page-load-timeout(Int $timeout_millis) { 132 | return self._timeouts( 'page load', $timeout_millis ); 133 | } 134 | 135 | # POST /session/:sessionId/timeouts/async_script 136 | method async-script-timeout(Int $timeout_millis) { 137 | return self._post( 'timeouts/async_script', { ms => $timeout_millis } ); 138 | } 139 | 140 | # POST /session/:sessionId/timeouts/implicit_wait 141 | method implicit-wait-timeout(Int $timeout_millis) { 142 | return self._post( 'timeouts/implicit_wait', { ms => $timeout_millis } ); 143 | } 144 | 145 | # GET /session/:sessionId/window_handle 146 | method current-window returns Selenium::WebDriver::WebWindow { 147 | my $handle = self._get( 'window_handle' ); 148 | return Selenium::WebDriver::WebWindow.new( :handle($handle), :driver(self) ); 149 | } 150 | 151 | # GET /session/:sessionId/window_handles 152 | method windows returns Array[Selenium::WebDriver::WebWindow] { 153 | my @handles = @( self._get( 'window_handles' ) ); 154 | my Selenium::WebDriver::WebWindow @results = gather { 155 | take Selenium::WebDriver::WebWindow.new( 156 | :handle($_), 157 | :driver(self) 158 | ) for @handles; 159 | }; 160 | 161 | return @results; 162 | } 163 | 164 | # GET /status 165 | method status returns Hash { 166 | return self._get( 'status' ); 167 | } 168 | 169 | # POST /session/:sessionId/execute 170 | =begin markdown 171 | 172 | my $script = q{ 173 | return arguments[0] + arguments[1]; 174 | }; 175 | my $result = $driver.execute( $script, [1, 2] ); 176 | 177 | =end markdown 178 | method execute(Str $script, Array $args = []) { 179 | my $result = self._post( 'execute', { script => $script, args => $args } ); 180 | return unless $result.defined; 181 | return $result; 182 | } 183 | 184 | # POST /session/:sessionId/execute_async 185 | =begin markdown 186 | 187 | my $script = q{ 188 | var callback = arguments[arguments.length-1]; 189 | callback( arguments[0] + arguments[1] ); 190 | }; 191 | my $result = $driver.execute-async( $script, [1, 2] ); 192 | 193 | =end markdown 194 | method execute-async(Str $script, Array $args = []) { 195 | my $result = self._post( 'execute_async', { script => $script, args => $args } ); 196 | return unless $result.defined; 197 | return $result; 198 | } 199 | 200 | # GET /session/:sessionId/ime/available_engines 201 | method ime-available-engines returns Array { 202 | return self._get( 'ime/available_engines' ); 203 | } 204 | 205 | # GET /session/:sessionId/ime/active_engine 206 | method ime-active-engine returns Str { 207 | return self._get( 'ime/active_engine' ); 208 | } 209 | 210 | # GET /session/:sessionId/ime/activated 211 | method ime-activated returns Bool { 212 | return self._get( 'ime/activated' ); 213 | } 214 | 215 | # POST /session/:sessionId/ime/deactivate 216 | method ime-deactivate { 217 | return self._post( 'ime/deactivate' ); 218 | } 219 | 220 | # POST /session/:sessionId/ime/activate 221 | method ime-activate(Str $engine) { 222 | return self._post( 'ime/activate', { engine => $engine } ); 223 | } 224 | 225 | # POST /session/:sessionId/frame 226 | method _frame(Any $id) { 227 | return self._post( 'frame', { id => $id } ); 228 | } 229 | 230 | multi method frame(Str $id) { 231 | return self._frame($id); 232 | } 233 | 234 | multi method frame(Int $id) { 235 | return self._frame($id); 236 | } 237 | 238 | multi method frame(Selenium::WebDriver::WebElement $id) { 239 | return self._frame($id); 240 | } 241 | 242 | # POST /session/:sessionId/frame/parent 243 | multi method frame-parent { 244 | return self._post( 'frame/parent' ); 245 | } 246 | 247 | # GET /session/:sessionId/cookie 248 | method cookies returns Array { 249 | return self._get( 'cookie' ); 250 | } 251 | 252 | # POST /session/:sessionId/cookie 253 | =begin markdown 254 | 255 | my $cookie = { 256 | name => 'Name', 257 | value => 'Value', 258 | path => '/', 259 | domain => 'domain.com', 260 | }; 261 | $driver.cookie( $cookie ); 262 | 263 | =end markdown 264 | multi method cookie(Hash $cookie) { 265 | return self._post( 'cookie', { cookie => $cookie } ); 266 | } 267 | 268 | # DELETE /session/:sessionId/cookie 269 | method delete-all-cookies { 270 | return self._delete( 'cookie' ); 271 | } 272 | 273 | # DELETE /session/:sessionId/cookie/:name 274 | method delete-cookie(Str $name) { 275 | return self._delete( "cookie/$name" ); 276 | } 277 | 278 | =begin markdown 279 | =end markdown 280 | # POST /session/:sessionId/url 281 | multi method url(Str $url) { 282 | return self._post( "url", { url => $url } ); 283 | } 284 | 285 | =begin markdown 286 | =end markdown 287 | # GET /session/:sessionId/url 288 | multi method url { 289 | return self._get( 'url' ); 290 | } 291 | 292 | =begin markdown 293 | =end markdown 294 | # GET /session/:sessionId/title 295 | method title { 296 | return self._get( 'title' ); 297 | } 298 | 299 | =begin markdown 300 | =end markdown 301 | # GET /session/:sessionId/source 302 | method source { 303 | return self._get( 'source' ); 304 | } 305 | 306 | =begin markdown 307 | =end markdown 308 | # POST /session/:sessionId/moveto 309 | method move-to(Str $element, Int $xoffset, Int $yoffset) { 310 | return self._post( 311 | "moveto", 312 | { 313 | element => $element, 314 | xoffset => $xoffset, 315 | yoffset => $yoffset, 316 | } 317 | ); 318 | } 319 | 320 | =begin markdown 321 | =end markdown 322 | # POST /session/:sessionId/click 323 | method click { 324 | return self._post( "click" ); 325 | } 326 | 327 | =begin markdown 328 | =end markdown 329 | method quit { 330 | self._delete_session if self.session-id.defined; 331 | LEAVE { 332 | self.stop; 333 | } 334 | }; 335 | 336 | =begin markdown 337 | =end markdown 338 | # GET /session/:sessionId/screenshot 339 | method screenshot { 340 | return self._get('screenshot'); 341 | } 342 | 343 | =begin markdown 344 | =end markdown 345 | method save-screenshot(Str $filename) { 346 | my $result = self.screenshot; 347 | $filename.IO.spurt(MIME::Base64.decode( $result )); 348 | } 349 | 350 | 351 | =begin markdown 352 | =end markdown 353 | # POST /session/:sessionId/forward 354 | method forward { 355 | return self._post( "forward" ); 356 | } 357 | 358 | =begin markdown 359 | =end markdown 360 | # POST /session/:sessionId/back 361 | method back { 362 | return self._post( "back" ); 363 | } 364 | 365 | =begin markdown 366 | =end markdown 367 | # POST /session/:sessionId/refresh 368 | method refresh { 369 | return self._post( "refresh" ); 370 | } 371 | 372 | =begin markdown 373 | =end markdown 374 | # POST /session/:sessionId/element 375 | method _element(Str $using, Str $value) { 376 | my $result = self._post( 377 | "element", 378 | { 379 | 'using' => $using, 380 | 'value' => $value, 381 | } 382 | ); 383 | 384 | return unless $result.defined; 385 | return Selenium::WebDriver::WebElement.new( 386 | :id( $result ), 387 | :driver( self ) 388 | ); 389 | } 390 | 391 | =begin markdown 392 | =end markdown 393 | method element-by-class(Str $class) { 394 | return self._element( 'class name', $class ); 395 | } 396 | 397 | =begin markdown 398 | =end markdown 399 | method element-by-css(Str $selector) { 400 | return self._element( 'css selector', $selector ); 401 | } 402 | 403 | =begin markdown 404 | =end markdown 405 | method element-by-id(Str $id) { 406 | return self._element( 'id', $id ); 407 | } 408 | 409 | =begin markdown 410 | =end markdown 411 | method element-by-name(Str $name) { 412 | return self._element( 'name', $name ); 413 | } 414 | 415 | =begin markdown 416 | =end markdown 417 | method element-by-link-text(Str $link-text) { 418 | return self._element( 'link text', $link-text ); 419 | } 420 | 421 | =begin markdown 422 | =end markdown 423 | method element-by-partial-link-text(Str $partial-link-text) { 424 | return self._element( 'partial link text', $partial-link-text ); 425 | } 426 | 427 | =begin markdown 428 | =end markdown 429 | method element-by-tag-name(Str $tag-name) { 430 | return self._element( 'tag name', $tag-name ); 431 | } 432 | 433 | =begin markdown 434 | =end markdown 435 | method element-by-xpath(Str $xpath) { 436 | return self._element( 'xpath', $xpath ); 437 | } 438 | 439 | =begin markdown 440 | =end markdown 441 | # POST /session/:sessionId/elements 442 | method _elements(Str $using, Str $value) { 443 | my $elements = self._post( 444 | "elements", 445 | { 446 | 'using' => $using, 447 | 'value' => $value, 448 | } 449 | ); 450 | 451 | return unless $elements.defined; 452 | my @results = $elements.map({ 453 | Selenium::WebDriver::WebElement.new( 454 | :id( $_ ), 455 | :driver( self ) 456 | ) 457 | }); 458 | 459 | return @results; 460 | } 461 | 462 | =begin markdown 463 | =end markdown 464 | method elements-by-class(Str $class) { 465 | return self._elements( 'class name', $class ); 466 | } 467 | 468 | =begin markdown 469 | =end markdown 470 | method elements-by-css(Str $selector) { 471 | return self._elements( 'css selector', $selector ); 472 | } 473 | 474 | =begin markdown 475 | =end markdown 476 | method elements-by-id(Str $id) { 477 | return self._elements( 'id', $id ); 478 | } 479 | 480 | =begin markdown 481 | =end markdown 482 | method elements-by-name(Str $name) { 483 | return self._elements( 'name', $name ); 484 | } 485 | 486 | =begin markdown 487 | =end markdown 488 | method elements-by-link-text(Str $link-text) { 489 | return self._elements( 'link text', $link-text ); 490 | } 491 | 492 | =begin markdown 493 | =end markdown 494 | method elements-by-partial-link-text(Str $partial-link-text) { 495 | return self._elements( 'partial link text', $partial-link-text ); 496 | } 497 | 498 | =begin markdown 499 | =end markdown 500 | method elements-by-tag-name(Str $tag-name) { 501 | return self._elements( 'tag name', $tag-name ); 502 | } 503 | 504 | =begin markdown 505 | =end markdown 506 | method elements-by-xpath(Str $xpath) { 507 | return self._elements( 'xpath', $xpath ); 508 | } 509 | 510 | =begin markdown 511 | =end markdown 512 | # POST /session/:sessionId/keys 513 | method send-keys-to-active-element(Str $keys) { 514 | return self._post( "keys", value => $keys.comb ); 515 | } 516 | 517 | # GET /session/:sessionId/orientation 518 | multi method orientation { 519 | return self._get( 'orientation' ); 520 | } 521 | 522 | # POST /session/:sessionId/orientation 523 | multi method orientation(Str $orientation) { 524 | return self._post( 'orientation', orientation => $orientation ); 525 | } 526 | 527 | # GET /session/:sessionId/alert_text 528 | multi method alert-text { 529 | return self._get( 'alert_text' ); 530 | } 531 | 532 | # POST /session/:sessionId/alert_text 533 | multi method alert-text(Str $text) { 534 | return self._post( 'alert_text', text => $text ); 535 | } 536 | 537 | # POST /session/:sessionId/accept_alert 538 | method accept-alert { 539 | return self._post( 'accept_alert' ); 540 | } 541 | 542 | # POST /session/:sessionId/dismiss_alert 543 | method dismiss-alert { 544 | return self._post( 'dismiss_alert' ); 545 | } 546 | 547 | # POST /session/:sessionId/buttondown 548 | method button-down(Int $button where $_ eq any(0..2)) { 549 | return self._post( 'buttondown', button => $button ); 550 | } 551 | 552 | # POST /session/:sessionId/buttonup 553 | method button-up(Int $button where $_ eq any(0..2)) { 554 | return self._post( 'buttonup', button => $button ); 555 | } 556 | 557 | # POST /session/:sessionId/doubleclick 558 | method double-click { 559 | return self._post( 'doubleclick' ); 560 | } 561 | 562 | # POST /session/:sessionId/touch/click 563 | method touch-click(Str $element) { 564 | return self._post( 'touch/click', { element => $element} ); 565 | } 566 | 567 | # POST /session/:sessionId/touch/down 568 | method touch-down(Int $x, Int $y) { 569 | return self._post( 'touch/down', { x => $x, y => $y } ); 570 | } 571 | 572 | # POST /session/:sessionId/touch/up 573 | method touch-up(Int $x, Int $y) { 574 | return self._post( 'touch/up', { x => $x, y => $y } ); 575 | } 576 | 577 | # POST session/:sessionId/touch/move 578 | method touch-move(Int $x, Int $y) { 579 | return self._post( 'touch/move', { x => $x, y => $y } ); 580 | } 581 | 582 | # POST session/:sessionId/touch/scroll 583 | multi method touch-scroll(Str $element, Int $x-offset, Int $y-offset) { 584 | return self._post( 585 | 'touch/scroll', 586 | { element => $element, xoffset => $x-offset, yoffset => $y-offset } 587 | ); 588 | } 589 | 590 | multi method touch-scroll(Int $x-offset, Int $y-offset) { 591 | return self._post( 592 | 'touch/scroll', 593 | { xoffset => $x-offset, yoffset => $y-offset } 594 | ); 595 | } 596 | 597 | # POST session/:sessionId/touch/doubleclick 598 | method touch-double-click(Str $element) { 599 | return self._post( 'touch/doubleclick', { element => $element} ); 600 | } 601 | 602 | # POST session/:sessionId/touch/longclick 603 | method touch-long-click(Str $element) { 604 | return self._post( 'touch/longclick', { element => $element} ); 605 | } 606 | 607 | # POST session/:sessionId/touch/flick 608 | multi method touch-flick( 609 | Str $element, 610 | Int $x-offset, 611 | Int $y-offset, 612 | Int $speed) 613 | { 614 | return self._post( 615 | 'touch/flick', 616 | { 617 | element => $element, 618 | xoffset => $x-offset, 619 | yoffset => $y-offset, 620 | speed => $speed 621 | } 622 | ); 623 | } 624 | 625 | multi method touch-flick(Int $x-speed, Int $y-speed) { 626 | return self._post( 627 | 'touch/flick', 628 | { 629 | xspeed => $x-speed, 630 | yspeed => $y-speed 631 | } 632 | ); 633 | } 634 | 635 | # GET /session/:sessionId/location 636 | multi method location { 637 | return self._get( 'location' ); 638 | } 639 | 640 | # POST /session/:sessionId/location 641 | multi method location(Hash $location) { 642 | return self._post( 'location', location => $location ); 643 | } 644 | 645 | # GET /session/:sessionId/local_storage 646 | method local-storage { 647 | return self._get( 'local_storage' ); 648 | } 649 | 650 | # POST /session/:sessionId/local_storage 651 | method add-to-local-storage(Str $key, Str $value) { 652 | return self._post( 'local_storage', key => $key, value => $value ); 653 | } 654 | 655 | # DELETE /session/:sessionId/local_storage 656 | method clear-local-storage { 657 | return self._delete( 'local_storage' ); 658 | } 659 | 660 | # GET /session/:sessionId/local_storage/key/:key 661 | method get-from-local-storage(Str $key) { 662 | return self._get( "local_storage/key/$key" ); 663 | } 664 | 665 | # DELETE /session/:sessionId/local_storage/key/:key 666 | method delete-from-local-storage(Str $key) { 667 | return self._delete( "local_storage/key/$key" ); 668 | } 669 | 670 | # GET /session/:sessionId/local_storage/size 671 | method local-storage-size(Str $key) returns Int { 672 | return self._get( 'local_storage/size' ); 673 | } 674 | 675 | # GET /session/:sessionId/session_storage 676 | method session-storage { 677 | return self._get( 'session_storage' ); 678 | } 679 | 680 | # POST /session/:sessionId/session_storage 681 | method add-to-session-storage(Str $key, Str $value) { 682 | return self._post( 'session_storage', key => $key, value => $value ); 683 | } 684 | 685 | # DELETE /session/:sessionId/session_storage 686 | method clear-session-storage { 687 | return self._delete( 'session_storage' ); 688 | } 689 | 690 | # GET /session/:sessionId/session_storage/key/:key 691 | method get-from-session-storage(Str $key) { 692 | return self._get( 'session_storage/key/$key' ); 693 | } 694 | 695 | # DELETE /session/:sessionId/session_storage/key/:key 696 | method delete-from-session-storage(Str $key) { 697 | return self._delete( 'session_storage/key/$key' ); 698 | } 699 | 700 | # GET /session/:sessionId/session_storage/size 701 | method session-storage-size(Str $key) returns Int { 702 | return self._get( 'session_storage/size' ); 703 | } 704 | 705 | # POST /session/:sessionId/log 706 | method log(Str $type) { 707 | return self._post( 'log', type => $type ); 708 | } 709 | 710 | # GET /session/:sessionId/log/types 711 | method log-types { 712 | return self._get( 'log/types' ); 713 | } 714 | 715 | # GET /session/:sessionId/application_cache/status 716 | method application-cache-status { 717 | return self._get( 'application_cache/status' ); 718 | } 719 | 720 | method _die(Str $method, Str $command, $response) { 721 | say "Died while doing '$method $command', content:\n"; 722 | say $response.content if $response.defined; 723 | say "end of content\n"; 724 | return unless $response.defined; 725 | my $o = from-json($response.content); 726 | 727 | my $error = $o; 728 | Selenium::WebDriver::X::Error.new( 729 | reason => $error // '', 730 | screenshot => $error, 731 | class => $error // '' 732 | ).throw; 733 | } 734 | =begin markdown 735 | =end markdown 736 | method _execute-command(Str $method, Str $command, Hash $params = {}) { 737 | say "$method $command with params " ~ $params.perl if self.debug; 738 | 739 | my $ua = HTTP::UserAgent.new; 740 | $ua.timeout = 5; 741 | my $url = uri_encode("http://" ~ self.host ~ ":" ~ self.port ~ self.url-prefix ~ $command); 742 | my $response; 743 | if ( $method eq 'POST' ) { 744 | my $content = to-json($params); 745 | my $request = HTTP::Request.new( 746 | :POST($url), 747 | :Content-Length($content.chars), 748 | :Content-Type("application/json;charset=UTF-8"), 749 | :Connection("close"), 750 | ); 751 | $request.add-content($content); 752 | $response = $ua.request($request); 753 | } 754 | elsif ( $method eq 'GET' ) { 755 | $response = $ua.get( $url ); 756 | } 757 | elsif ( $method eq 'DELETE' ) { 758 | my $request = HTTP::Request.new( 759 | :DELETE($url), 760 | :Content-Type("application/json;charset=UTF-8"), 761 | :Connection("close"), 762 | ); 763 | $response = $ua.request($request); 764 | } 765 | else { 766 | die qq{Unknown method "$method"}; 767 | } 768 | 769 | # Since we didnt get a response here, return nothing 770 | return unless $response.defined; 771 | 772 | my $result; 773 | if ( $response.is-success ) { 774 | $result = from-json( $response.content ); 775 | } 776 | else { 777 | self._die($method, $command, $response); 778 | warn "FAILED: " ~ $response.status-line if self.debug; 779 | } 780 | 781 | return $result; 782 | } 783 | 784 | method _get(Str $command) { 785 | my $result = self._execute-command( 786 | "GET", 787 | "/session/$(self.session-id)/$command", 788 | ); 789 | 790 | return unless $result.defined; 791 | return $result; 792 | } 793 | 794 | method _post(Str $command, Hash $params = {}) { 795 | return self._execute-command( 796 | "POST", 797 | "/session/$(self.session-id)/$command", 798 | $params 799 | ); 800 | } 801 | 802 | method _delete(Str $command, Hash $params = {}) { 803 | return self._execute-command( 804 | "DELETE", 805 | "/session/$(self.session-id)/$command", 806 | $params 807 | ); 808 | } 809 | 810 | =begin markdown 811 | ### _empty-port 812 | 813 | Find a random port in the dynamic/private range 814 | 815 | According to [IANA](http://www.iana.org/assignments/port-numbers), dynamic 816 | and/or private are in the range 49152 to 65535 817 | 818 | =end markdown 819 | method _empty-port { 820 | while 1 { 821 | # Find a random port in the dynamic/private range 822 | my $port = (49152..65535).pick; 823 | 824 | # Open and close a TCP connection to it 825 | my $socket = IO::Socket::INET.new( :host('127.0.0.1'), :port($port) ); 826 | $socket.close; 827 | 828 | CATCH { 829 | default { 830 | # If it fails, it may mean that this port is not bound 831 | return $port; 832 | } 833 | } 834 | } 835 | } 836 | -------------------------------------------------------------------------------- /lib/Selenium/WebDriver/X/Error.rakumod: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | unit class Selenium::WebDriver::X::Error is Exception; 5 | 6 | has $.reason is rw; 7 | has $.screenshot is rw; 8 | has $.class is rw; 9 | 10 | method message { 11 | return 12 | "\n" ~ ("-" x 80) ~ "\n" ~ 13 | "Error:\n" ~ 14 | "Reason: \n" ~ $.reason ~ "\n" ~ 15 | "Type: \n" ~ $.class ~ "\n" ~ 16 | "Has a screenshot:\n " ~ $.screenshot.defined ~ "\n" ~ 17 | ("-" x 80); 18 | } 19 | -------------------------------------------------------------------------------- /logotype/logo_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azawawi/raku-selenium-webdriver/ec6e0991800a5e5daf56ae6cfa191d3efa63856d/logotype/logo_32x32.png -------------------------------------------------------------------------------- /resources/firefox_extension/prefs.json: -------------------------------------------------------------------------------- 1 | { 2 | "frozen": { 3 | "app.update.auto": false, 4 | "app.update.enabled": false, 5 | "browser.displayedE10SNotice": 4, 6 | "browser.download.manager.showWhenStarting": false, 7 | "browser.EULA.override": true, 8 | "browser.EULA.3.accepted": true, 9 | "browser.link.open_external": 2, 10 | "browser.link.open_newwindow": 2, 11 | "browser.offline": false, 12 | "browser.reader.detectedFirstArticle": true, 13 | "browser.safebrowsing.enabled": false, 14 | "browser.safebrowsing.malware.enabled": false, 15 | "browser.search.update": false, 16 | "browser.selfsupport.url" : "", 17 | "browser.sessionstore.resume_from_crash": false, 18 | "browser.shell.checkDefaultBrowser": false, 19 | "browser.tabs.warnOnClose": false, 20 | "browser.tabs.warnOnOpen": false, 21 | "datareporting.healthreport.service.enabled": false, 22 | "datareporting.healthreport.uploadEnabled": false, 23 | "datareporting.healthreport.service.firstRun": false, 24 | "datareporting.healthreport.logging.consoleEnabled": false, 25 | "datareporting.policy.dataSubmissionEnabled": false, 26 | "datareporting.policy.dataSubmissionPolicyAccepted": false, 27 | "devtools.errorconsole.enabled": true, 28 | "dom.disable_open_during_load": false, 29 | "extensions.autoDisableScopes": 10, 30 | "extensions.blocklist.enabled": false, 31 | "extensions.logging.enabled": true, 32 | "extensions.update.enabled": false, 33 | "extensions.update.notifyUser": false, 34 | "javascript.enabled": true, 35 | "network.manage-offline-status": false, 36 | "network.http.phishy-userpass-length": 255, 37 | "offline-apps.allow_by_default": true, 38 | "prompts.tab_modal.enabled": false, 39 | "security.csp.enable": false, 40 | "security.fileuri.origin_policy": 3, 41 | "security.fileuri.strict_origin_policy": false, 42 | "security.warn_entering_secure": false, 43 | "security.warn_entering_secure.show_once": false, 44 | "security.warn_entering_weak": false, 45 | "security.warn_entering_weak.show_once": false, 46 | "security.warn_leaving_secure": false, 47 | "security.warn_leaving_secure.show_once": false, 48 | "security.warn_submit_insecure": false, 49 | "security.warn_viewing_mixed": false, 50 | "security.warn_viewing_mixed.show_once": false, 51 | "signon.rememberSignons": false, 52 | "toolkit.networkmanager.disable": true, 53 | "toolkit.telemetry.prompted": 2, 54 | "toolkit.telemetry.enabled": false, 55 | "toolkit.telemetry.rejected": true, 56 | "xpinstall.signatures.required": false 57 | }, 58 | "mutable": { 59 | "browser.dom.window.dump.enabled": true, 60 | "browser.newtab.url": "about:blank", 61 | "browser.newtabpage.enabled": false, 62 | "browser.startup.page": 0, 63 | "browser.startup.homepage": "about:blank", 64 | "dom.max_chrome_script_run_time": 30, 65 | "dom.max_script_run_time": 30, 66 | "dom.report_all_js_exceptions": true, 67 | "javascript.options.showInConsole": true, 68 | "network.http.max-connections-per-server": 10, 69 | "startup.homepage_welcome_url": "about:blank", 70 | "webdriver_accept_untrusted_certs": true, 71 | "webdriver_assume_untrusted_issuer": true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /resources/firefox_extension/webdriver.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azawawi/raku-selenium-webdriver/ec6e0991800a5e5daf56ae6cfa191d3efa63856d/resources/firefox_extension/webdriver.xpi -------------------------------------------------------------------------------- /t/01-load.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | plan 1; 7 | 8 | use Selenium::WebDriver; 9 | ok 1, "'use Selenium::WebDriver' worked!"; 10 | -------------------------------------------------------------------------------- /t/02-phantomjs.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'url', 'source', 'move-to', 'click', 'quit', 8 | 'screenshot', 'save-screenshot', 'forward', 'back', 'refresh', 9 | 'element-by-class', 'element-by-css', 'element-by-id', 10 | 'element-by-name', 'element-by-link-text', 'element-by-partial-link-text', 11 | 'element-by-tag-name', 'element-by-xpath', 'elements-by-class', 12 | 'elements-by-css', 'elements-by-id', 'elements-by-name', 13 | 'elements-by-link-text', 'elements-by-partial-link-text', 14 | 'elements-by-tag-name', 'elements-by-xpath', 'sessions', 'capabilities', 15 | 'script-timeout', 'implicit-timeout', 'page-load-timeout', 16 | 'current-window', 'windows', 'status', 17 | 'async-script-timeout', 'implicit-wait-timeout', 'execute', 'execute-async', 18 | 'ime-available-engines', 'ime-active-engine', 'ime-activated', 19 | 'ime-activated', 'ime-activated', 'frame', 'frame-parent', 20 | 'cookies', 'cookie', 'delete-all-cookies', 'delete-cookie', 21 | 'send-keys-to-active-element', 'orientation', 'alert-text', 'accept-alert', 22 | 'dismiss-alert', 'button-down', 'button-up', 'double-click', 'touch-click', 23 | 'touch-down', 'touch-up', 'touch-move', 'touch-scroll', 'touch-double-click', 24 | 'touch-long-click', 'touch-flick', 'location', 25 | 'local-storage', 'add-to-local-storage', 'clear-local-storage', 26 | 'get-from-local-storage', 'delete-from-local-storage', 'local-storage-size', 27 | 'session-storage', 'add-to-session-storage', 'clear-session-storage', 28 | 'get-from-session-storage', 'delete-from-session-storage', 29 | 'session-storage-size', 'log', 'log-types', 'application-cache-status'; 30 | 31 | plan @methods.elems + 14; 32 | 33 | use Selenium::WebDriver::PhantomJS; 34 | ok 1, "'use Selenium::WebDriver::PhantomJS' worked!"; 35 | 36 | { 37 | # Skip tests if phantomjs is not found 38 | use File::Which; 39 | unless which('phantomjs') { 40 | skip-rest("phantomjs is not installed. skipping tests..."); 41 | exit; 42 | } 43 | } 44 | 45 | my $driver = Selenium::WebDriver::PhantomJS.new(:debug, :max-attempts(30), :port(5555)); 46 | ok $driver, "Selenium::WebDriver::PhantomJS.new worked"; 47 | 48 | for @methods -> $method { 49 | ok Selenium::WebDriver::PhantomJS.can($method), 50 | "Selenium::WebDriver::PhantomJS.$method is found"; 51 | } 52 | 53 | { 54 | my $sessions = $driver.sessions; 55 | ok $sessions.defined, "Sessions returned a defined value"; 56 | ok $sessions ~~ Array, "Sessions is an array"; 57 | ok $sessions.elems == 1, "Only One session should be there"; 58 | ok $sessions[0] ~~ Str, "And we have a sessionId"; 59 | } 60 | 61 | { 62 | my $capabilities = $driver.capabilities; 63 | ok $capabilities.defined, "capabilities returned a defined value"; 64 | ok $capabilities ~~ Hash, "capabilities is a hash"; 65 | ok $capabilities ~~ Str, "And we have a sessionId"; 66 | } 67 | 68 | { 69 | my $current-window = $driver.current-window; 70 | ok $current-window.defined, "current-window returned a defined value"; 71 | ok $current-window.handle ~~ Str, "current-window handle is a string"; 72 | } 73 | 74 | { 75 | my @windows = $driver.windows; 76 | ok @windows.defined, "windows returned a defined value"; 77 | ok @windows.elems > 0, "windows has at least one active window"; 78 | ok @windows[0] ~~ Selenium::WebDriver::WebWindow, "first element is a window"; 79 | } 80 | 81 | LEAVE { 82 | $driver.quit if $driver.defined; 83 | } 84 | -------------------------------------------------------------------------------- /t/03-webelement.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'click', 'send-keys', 'tag-name', 'selected', 'enabled', 'attr', 8 | 'equals-by-id', 'displayed', 'location', 'location-in-view', 'size' , 9 | 'css', 'submit', 'text', 'clear'; 10 | 11 | plan @methods.elems + 2; 12 | 13 | use Selenium::WebDriver::WebElement; 14 | ok 1, "'use Selenium::WebDriver::WebElement' worked!"; 15 | 16 | my $element = Selenium::WebDriver::WebElement.new; 17 | ok $element, "Selenium::WebDriver::WebElement.new worked"; 18 | 19 | for @methods -> $method { 20 | ok Selenium::WebDriver::WebElement.can($method), 21 | "Selenium::WebDriver::WebElement.$method is found"; 22 | } 23 | -------------------------------------------------------------------------------- /t/04-webwindow.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'current', 'close-current', 'size', 'position', 'maximize'; 8 | 9 | plan @methods.elems + 2; 10 | 11 | use Selenium::WebDriver::WebWindow; 12 | ok 1, "'use Selenium::WebDriver::WebWindow' worked!"; 13 | 14 | my $element = Selenium::WebDriver::WebWindow.new; 15 | ok $element, "Selenium::WebDriver::WebWindow.new worked"; 16 | 17 | for @methods -> $method { 18 | ok Selenium::WebDriver::WebWindow.can($method), 19 | "Selenium::WebDriver::WebWindow.$method is found"; 20 | } 21 | -------------------------------------------------------------------------------- /t/05-firefox.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'url', 'source', 'move-to', 'click', 'quit', 8 | 'screenshot', 'save-screenshot', 'forward', 'back', 'refresh', 9 | 'element-by-class', 'element-by-css', 'element-by-id', 10 | 'element-by-name', 'element-by-link-text', 'element-by-partial-link-text', 11 | 'element-by-tag-name', 'element-by-xpath', 'elements-by-class', 12 | 'elements-by-css', 'elements-by-id', 'elements-by-name', 13 | 'elements-by-link-text', 'elements-by-partial-link-text', 14 | 'elements-by-tag-name', 'elements-by-xpath', 'sessions', 'capabilities', 15 | 'script-timeout', 'implicit-timeout', 'page-load-timeout', 16 | 'current-window', 'windows', 'status', 17 | 'async-script-timeout', 'implicit-wait-timeout', 'execute', 'execute-async', 18 | 'ime-available-engines', 'ime-active-engine', 'ime-activated', 19 | 'ime-activated', 'ime-activated', 'frame', 'frame-parent', 20 | 'cookies', 'cookie', 'delete-all-cookies', 'delete-cookie', 21 | 'send-keys-to-active-element', 'orientation', 'alert-text', 'accept-alert', 22 | 'dismiss-alert', 'button-down', 'button-up', 'double-click', 'touch-click', 23 | 'touch-down', 'touch-up', 'touch-move', 'touch-scroll', 'touch-double-click', 24 | 'touch-long-click', 'touch-flick', 'location', 25 | 'local-storage', 'add-to-local-storage', 'clear-local-storage', 26 | 'get-from-local-storage', 'delete-from-local-storage', 'local-storage-size', 27 | 'session-storage', 'add-to-session-storage', 'clear-session-storage', 28 | 'get-from-session-storage', 'delete-from-session-storage', 29 | 'session-storage-size', 'log', 'log-types', 'application-cache-status'; 30 | 31 | plan @methods.elems + 10; 32 | 33 | use Selenium::WebDriver::Firefox; 34 | ok 1, "'use Selenium::WebDriver::Firefox' worked!"; 35 | 36 | { 37 | # Skip tests if firefox is not found 38 | use File::Which; 39 | unless which('firefox') { 40 | skip-rest("Firefox is not installed. skipping tests..."); 41 | exit; 42 | } 43 | } 44 | 45 | my $driver = Selenium::WebDriver::Firefox.new; 46 | ok $driver, "Selenium::WebDriver::Firefox.new worked"; 47 | 48 | for @methods -> $method { 49 | ok Selenium::WebDriver::Firefox.can($method), 50 | "Selenium::WebDriver::Firefox.$method is found"; 51 | } 52 | 53 | #{ 54 | # my $sessions = $driver.sessions; 55 | # ok $sessions.defined, "Sessions returned a defined value"; 56 | # ok $sessions ~~ Array, "Sessions is an array"; 57 | # ok $sessions.elems == 1, "Only One session should be there"; 58 | # ok $sessions[0] ~~ Str, "And we have a sessionId"; 59 | #} 60 | 61 | { 62 | my $capabilities = $driver.capabilities; 63 | ok $capabilities.defined, "capabilities returned a defined value"; 64 | ok $capabilities ~~ Hash, "capabilities is a hash"; 65 | ok $capabilities ~~ Str, "And we have a sessionId"; 66 | } 67 | 68 | { 69 | my $current-window = $driver.current-window; 70 | ok $current-window.defined, "current-window returned a defined value"; 71 | ok $current-window.handle ~~ Str, "current-window handle is a string"; 72 | } 73 | 74 | { 75 | my @windows = $driver.windows; 76 | ok @windows.defined, "windows returned a defined value"; 77 | ok @windows.elems > 0, "windows has at least one active window"; 78 | ok @windows[0] ~~ Selenium::WebDriver::WebWindow, "first element is a window"; 79 | } 80 | 81 | LEAVE { 82 | $driver.quit if $driver.defined; 83 | } 84 | -------------------------------------------------------------------------------- /t/06-chrome.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'url', 'source', 'move-to', 'click', 'quit', 8 | 'screenshot', 'save-screenshot', 'forward', 'back', 'refresh', 9 | 'element-by-class', 'element-by-css', 'element-by-id', 10 | 'element-by-name', 'element-by-link-text', 'element-by-partial-link-text', 11 | 'element-by-tag-name', 'element-by-xpath', 'elements-by-class', 12 | 'elements-by-css', 'elements-by-id', 'elements-by-name', 13 | 'elements-by-link-text', 'elements-by-partial-link-text', 14 | 'elements-by-tag-name', 'elements-by-xpath', 'sessions', 'capabilities', 15 | 'script-timeout', 'implicit-timeout', 'page-load-timeout', 16 | 'current-window', 'windows', 'status', 17 | 'async-script-timeout', 'implicit-wait-timeout', 'execute', 'execute-async', 18 | 'ime-available-engines', 'ime-active-engine', 'ime-activated', 19 | 'ime-activated', 'ime-activated', 'frame', 'frame-parent', 20 | 'cookies', 'cookie', 'delete-all-cookies', 'delete-cookie', 21 | 'send-keys-to-active-element', 'orientation', 'alert-text', 'accept-alert', 22 | 'dismiss-alert', 'button-down', 'button-up', 'double-click', 'touch-click', 23 | 'touch-down', 'touch-up', 'touch-move', 'touch-scroll', 'touch-double-click', 24 | 'touch-long-click', 'touch-flick', 'location', 25 | 'local-storage', 'add-to-local-storage', 'clear-local-storage', 26 | 'get-from-local-storage', 'delete-from-local-storage', 'local-storage-size', 27 | 'session-storage', 'add-to-session-storage', 'clear-session-storage', 28 | 'get-from-session-storage', 'delete-from-session-storage', 29 | 'session-storage-size', 'log', 'log-types', 'application-cache-status'; 30 | 31 | plan @methods.elems + 14; 32 | 33 | use Selenium::WebDriver::Chrome; 34 | ok 1, "'use Selenium::WebDriver::Chrome' worked!"; 35 | 36 | { 37 | # Skip tests if chromedriver is not found 38 | use File::Which; 39 | unless which('chromedriver') { 40 | skip-rest("chromedriver is not installed. skipping tests..."); 41 | exit; 42 | } 43 | } 44 | 45 | my $driver = Selenium::WebDriver::Chrome.new; 46 | ok $driver, "Selenium::WebDriver::Chrome.new worked"; 47 | 48 | for @methods -> $method { 49 | ok Selenium::WebDriver::Chrome.can($method), 50 | "Selenium::WebDriver::Chrome.$method is found"; 51 | } 52 | 53 | { 54 | my $sessions = $driver.sessions; 55 | ok $sessions.defined, "Sessions returned a defined value"; 56 | ok $sessions ~~ Array, "Sessions is an array"; 57 | ok $sessions.elems == 1, "Only One session should be there"; 58 | ok $sessions[0] ~~ Str, "And we have a sessionId"; 59 | } 60 | 61 | { 62 | my $capabilities = $driver.capabilities; 63 | ok $capabilities.defined, "capabilities returned a defined value"; 64 | ok $capabilities ~~ Hash, "capabilities is a hash"; 65 | ok $capabilities ~~ Str, "And we have a sessionId"; 66 | } 67 | 68 | { 69 | my $current-window = $driver.current-window; 70 | ok $current-window.defined, "current-window returned a defined value"; 71 | ok $current-window.handle ~~ Str, "current-window handle is a string"; 72 | } 73 | 74 | { 75 | my @windows = $driver.windows; 76 | ok @windows.defined, "windows returned a defined value"; 77 | ok @windows.elems > 0, "windows has at least one active window"; 78 | ok @windows[0] ~~ Selenium::WebDriver::WebWindow, "first element is a window"; 79 | } 80 | 81 | LEAVE { 82 | $driver.quit if $driver.defined; 83 | } 84 | -------------------------------------------------------------------------------- /t/07-blackberry.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | 3 | use Test; 4 | use lib 'lib'; 5 | 6 | # Methods to test 7 | my @methods = 'url', 'source', 'move-to', 'click', 'quit', 8 | 'screenshot', 'save-screenshot', 'forward', 'back', 'refresh', 9 | 'element-by-class', 'element-by-css', 'element-by-id', 10 | 'element-by-name', 'element-by-link-text', 'element-by-partial-link-text', 11 | 'element-by-tag-name', 'element-by-xpath', 'elements-by-class', 12 | 'elements-by-css', 'elements-by-id', 'elements-by-name', 13 | 'elements-by-link-text', 'elements-by-partial-link-text', 14 | 'elements-by-tag-name', 'elements-by-xpath', 'sessions', 'capabilities', 15 | 'script-timeout', 'implicit-timeout', 'page-load-timeout', 16 | 'current-window', 'windows', 'status', 17 | 'async-script-timeout', 'implicit-wait-timeout', 'execute', 'execute-async', 18 | 'ime-available-engines', 'ime-active-engine', 'ime-activated', 19 | 'ime-activated', 'ime-activated', 'frame', 'frame-parent', 20 | 'cookies', 'cookie', 'delete-all-cookies', 'delete-cookie', 21 | 'send-keys-to-active-element', 'orientation', 'alert-text', 'accept-alert', 22 | 'dismiss-alert', 'button-down', 'button-up', 'double-click', 'touch-click', 23 | 'touch-down', 'touch-up', 'touch-move', 'touch-scroll', 'touch-double-click', 24 | 'touch-long-click', 'touch-flick', 'location', 25 | 'local-storage', 'add-to-local-storage', 'clear-local-storage', 26 | 'get-from-local-storage', 'delete-from-local-storage', 'local-storage-size', 27 | 'session-storage', 'add-to-session-storage', 'clear-session-storage', 28 | 'get-from-session-storage', 'delete-from-session-storage', 29 | 'session-storage-size', 'log', 'log-types', 'application-cache-status'; 30 | 31 | plan @methods.elems + 14; 32 | 33 | use Selenium::WebDriver::BlackBerry; 34 | ok 1, "'use Selenium::WebDriver::BlackBerry' worked!"; 35 | 36 | unless %*ENV { 37 | skip-rest("Environment variable SELENIUM_BLACKBERRY_IP is not set. skipping tests..."); 38 | exit; 39 | } 40 | 41 | my $driver = Selenium::WebDriver::BlackBerry.new(%*ENV); 42 | ok $driver, "Selenium::WebDriver::BlackBerry.new worked"; 43 | 44 | for @methods -> $method { 45 | ok Selenium::WebDriver::BlackBerry.can($method), 46 | "Selenium::WebDriver::BlackBerry.$method is found"; 47 | } 48 | 49 | { 50 | my $sessions = $driver.sessions; 51 | ok $sessions.defined, "Sessions returned a defined value"; 52 | ok $sessions ~~ Array, "Sessions is an array"; 53 | ok $sessions.elems == 1, "Only One session should be there"; 54 | ok $sessions[0] ~~ Int, "And we have a sessionId"; 55 | } 56 | 57 | { 58 | my $capabilities = $driver.capabilities; 59 | ok $capabilities.defined, "capabilities returned a defined value"; 60 | ok $capabilities ~~ Hash, "capabilities is a hash"; 61 | ok $capabilities ~~ Str, "And we have a sessionId"; 62 | } 63 | 64 | { 65 | my $current-window = $driver.current-window; 66 | ok $current-window.defined, "current-window returned a defined value"; 67 | ok $current-window.handle ~~ Str, "current-window handle is a string"; 68 | } 69 | 70 | { 71 | my @windows = $driver.windows; 72 | ok @windows.defined, "windows returned a defined value"; 73 | ok @windows.elems > 0, "windows has at least one active window"; 74 | ok @windows[0] ~~ Selenium::WebDriver::WebWindow, "first element is a window"; 75 | } 76 | 77 | # This would make the BlackBerry Browser quit. So we avoid that. 78 | # LEAVE { 79 | # $driver.quit if $driver.defined; 80 | # } 81 | -------------------------------------------------------------------------------- /t/99-author-meta.rakutest: -------------------------------------------------------------------------------- 1 | 2 | use v6; 3 | 4 | use Test; 5 | 6 | plan 1; 7 | 8 | if ?%*ENV { 9 | require Test::META <&meta-ok>; 10 | meta-ok; 11 | done-testing; 12 | } else { 13 | skip-rest "Skipping author test"; 14 | exit; 15 | } 16 | --------------------------------------------------------------------------------