├── .gitignore
├── .mailmap
├── .travis.yml
├── Changes
├── INSTALL.md
├── MANIFEST.SKIP
├── README.md
├── TAGS
├── at
├── Waiter.t
├── chrome.test
├── edge.test
├── firefox.test
├── legacy.test
├── other.html
├── sanity-chrome.test
├── sanity-edge.test
├── sanity-ie.test
├── sanity-safari.test
├── sanity.test
├── test-firefox.test
└── test.html
├── cpanfile
├── dist.ini
├── driver-example.pl
├── lib
├── Selenium
│ ├── ActionChains.pm
│ ├── CanStartBinary.pm
│ ├── CanStartBinary
│ │ ├── FindBinary.pm
│ │ └── ProbePort.pm
│ ├── Chrome.pm
│ ├── Edge.pm
│ ├── Firefox.pm
│ ├── Firefox
│ │ ├── Binary.pm
│ │ ├── Profile.pm
│ │ └── webdriver.xpi
│ ├── InternetExplorer.pm
│ ├── PhantomJS.pm
│ ├── Remote
│ │ ├── Commands.pm
│ │ ├── Driver.pm
│ │ ├── Driver
│ │ │ ├── CanSetWebdriverContext.pm
│ │ │ └── Firefox
│ │ │ │ └── Profile.pm
│ │ ├── ErrorHandler.pm
│ │ ├── Finders.pm
│ │ ├── Mock
│ │ │ ├── Commands.pm
│ │ │ └── RemoteConnection.pm
│ │ ├── RemoteConnection.pm
│ │ ├── Spec.pm
│ │ ├── WDKeys.pm
│ │ └── WebElement.pm
│ └── Waiter.pm
└── Test
│ └── Selenium
│ ├── Chrome.pm
│ ├── Edge.pm
│ ├── Firefox.pm
│ ├── InternetExplorer.pm
│ ├── PhantomJS.pm
│ └── Remote
│ ├── Driver.pm
│ ├── Role
│ └── DoesTesting.pm
│ └── WebElement.pm
├── perlcriticrc
├── t
├── 00-load.t
├── 01-driver-pac.t
├── 01-driver.t
├── 01-webdriver3.t
├── 02-webelement.t
├── 03-spec-coverage.t
├── 04-commands-implemented.t
├── 10-switch-to-window.t
├── 12-reuse-session.t
├── 13-waiter.t
├── CanSetWebdriverContext.t
├── Finders.t
├── Firefox-Profile.t
├── Remote-Connection.t
├── Test-Selenium-Remote-Driver-google.t
├── Test-Selenium-Remote-Driver.t
├── Test-Selenium-Remote-WebElement.t
├── bin
│ ├── docker-record-linux
│ └── record.pl
├── convenience.t
├── error.t
├── http-server.pl
├── lib
│ └── TestHarness.pm
├── mock-recordings
│ ├── 01-driver-mock.json
│ ├── 02-webelement-mock.json
│ ├── 10-switch-to-window-mock.json
│ ├── 12-reuse-session-mock.json
│ ├── convenience-mock-darwin.json
│ ├── convenience-mock.json
│ ├── finders-mock.json
│ ├── firefox-profile-mock.json
│ └── test-selenium-remote-driver-google-mock.json
├── uploadTest
└── www
│ ├── 404.html
│ ├── alerts.html
│ ├── cookies.html
│ ├── dragAndDropTest.html
│ ├── encoded_profile.b64
│ ├── formPage.html
│ ├── frameset.html
│ ├── icon.gif
│ ├── iframes.html
│ ├── index.html
│ ├── invalid-extension.xpi
│ ├── javascriptPage.html
│ ├── metakeys.html
│ ├── nestedElements.html
│ ├── popup.html
│ ├── redisplay.xpi
│ └── xhtmlTest.html
├── tidyall.ini
└── weaver.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | META.yml
2 | MYMETA.yml
3 | Makefile*
4 | blib
5 | pm_to_blib
6 | cover_db
7 | *.komodo*
8 | *.sublime*
9 | .build
10 | Selenium-Remote-Driver-*
11 | selenium*.jar
12 | .prove
13 | _prove
14 | #vim swapfiles
15 | *.swo
16 | *.swp
17 | *.bak
18 |
--------------------------------------------------------------------------------
/.mailmap:
--------------------------------------------------------------------------------
1 | Aditya Ivaturi
2 | Charles Howes
3 | Charles Howes
4 | Daniel Gempesaw
5 | Daniel Gempesaw
6 | Daniel Gempesaw
7 | Emmanuel Peroumalnaïk
8 | Emmanuel Peroumalnaïk
9 | Emmanuel Peroumalnaïk
10 | Gordon Child
11 | Gordon Child
12 | Mark Stosberg
13 | Bas Bloemsaat
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: perl
3 | perl:
4 | - '5.28'
5 | - '5.26'
6 | - '5.24'
7 | - '5.22'
8 | - '5.20'
9 | matrix:
10 | fast_finish: true
11 | include:
12 | - perl: '5.30'
13 | env: COVERAGE=1
14 | before_install:
15 | - git config --global user.name "TravisCI"
16 | - git config --global user.email $HOSTNAME":not-for-mail@travis-ci.org"
17 | - git clone git://github.com/travis-perl/helpers ~/travis-perl-helpers
18 | - source ~/travis-perl-helpers/init
19 | - build-perl
20 | - perl -V
21 | install:
22 | - cpan-install --coverage # installs converage prereqs, if enabled
23 | - cpanm --quiet --notest Devel::Cover::Report::Coveralls #send to coveralls
24 | - cpanm --quiet --notest Dist::Zilla::App::Command::cover #make sure we can dzil cover
25 | - cpanm --quiet --notest --skip-satisfied Dist::Zilla #unfortunately, we need a very new (6.0 or better) dzil, so no perl < 5.14
26 | - cpanm --quiet --notest --skip-satisfied Test::Spec WWW::Mechanize Test::WWW::Selenium #Test::Pod::Coverage can be stupid
27 | - "dzil authordeps --missing | grep -vP '[^\\w:]' | xargs -n 5 -P 10 cpanm --quiet --notest"
28 | - "dzil listdeps --author --missing | grep -vP '[^\\w:]' | xargs -n 5 -P 10 cpanm --quiet --notest"
29 | before_script:
30 | - coverage-setup
31 | script:
32 | - AUTHOR_TESTING=1 RELEASE_TESTING=1 dzil cover
33 | - export BUILDDIR=`find .build -name cover_db`; cd `dirname $BUILDDIR`
34 | after_success:
35 | - coverage-report
36 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | ## Installation
2 |
3 | It's probably easiest to use the `cpanm` or `CPAN` commands:
4 |
5 | ```bash
6 | $ cpanm Selenium::Remote::Driver
7 | ```
8 |
9 | If you want to install from this repository, you have a few options;
10 | see the [installation docs][] for more details.
11 |
12 | [installation docs]: /install.md
13 |
14 | ### With Dist::Zilla
15 |
16 | If you have Dist::Zilla, it's straightforward:
17 |
18 | ```bash
19 | $ dzil listdeps --missing | cpanm
20 | $ dzil install
21 | ```
22 |
23 | ### Without Dist::Zilla
24 |
25 | We maintain two branches that have `Makefile.PL`:
26 | [`cpan`][cpan-branch] and [`build/master`][bm-branch]. The `cpan`
27 | branch is only updated every time we release to the CPAN, and it is
28 | not kept up to date with master. The `build/master` branch is an
29 | up-to-date copy of the latest changes in master, and will usually
30 | contain changes that have not made it to a CPAN release yet.
31 |
32 | To get either of these, you can use the following, (replacing
33 | "build/master" with "cpan" if desired):
34 |
35 | ```bash
36 | $ cpanm -v git://github.com/gempesaw/Selenium-Remote-Driver.git@build/master
37 | ```
38 |
39 | Or, without `cpanm` and/or without the `git://` protocol:
40 |
41 | ```bash
42 | $ git clone https://github.com/gempesaw/Selenium-Remote-Driver --branch build/master --single-branch --depth 1
43 | $ cd Selenium-Remote-Driver
44 | $ perl Makefile.PL
45 | ```
46 |
47 | Note that due to POD::Weaver, the line numbers between these generated
48 | branches and the master branch are unfortunately completely
49 | incompatible.
50 |
51 | [cpan-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/cpan
52 | [bm-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/build/master
53 |
54 | ### Viewing dependencies
55 |
56 | You can also use `cpanm` to help you with dependencies after you've
57 | cloned the repository:
58 |
59 | ```bash
60 | $ cpanm --showdeps .
61 | ```
62 |
--------------------------------------------------------------------------------
/MANIFEST.SKIP:
--------------------------------------------------------------------------------
1 | MANIFEST.SKIP
2 | cover_db/*
3 | .travis.yml
4 | weaver.ini
5 | .git/*
6 |
--------------------------------------------------------------------------------
/at/Waiter.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Test::More;
4 | use Test::Warn;
5 | use Test::Fatal;
6 | use Test::Time;
7 | use Selenium::Waiter;
8 |
9 | SIMPLE_WAIT: {
10 | my $ret;
11 | waits_ok( sub { $ret = wait_until { 1 } }, '<', 2, 'immediately true returns quickly' );
12 | ok($ret == 1, 'return value for a true wait_until is passed up');
13 |
14 | waits_ok( sub { $ret = wait_until { 0 } }, '==', 30, 'never true expires the timeout' );
15 | ok($ret eq '', 'return value for a false wait is an empty string');
16 | }
17 |
18 | EVENTUALLY: {
19 | my $ret = 0;
20 | waits_ok( sub { wait_until { $ret++ > 2 } }, '>', 2, 'eventually true takes time');
21 |
22 | $ret = 0;
23 | my %opts = ( interval => 2, timeout => 5 );
24 | waits_ok(
25 | sub { wait_until { $ret++; 0 } %opts }, '>', 4,
26 | 'timeout is respected'
27 | );
28 | ok(1 <= $ret && $ret <= 3, 'interval option changes iteration speed');
29 | }
30 |
31 | EXCEPTIONS: {
32 | my %opts = ( timeout => 2 );
33 | warning_is { wait_until { die 'caught!' } %opts } 'caught!',
34 | 'exceptions usually only warn once';
35 | }
36 |
37 | NO_FINALLY_WAIT: {
38 | waits_ok( sub { wait_until { 1 }, interval => 10 }, '<', 1,
39 | 'does not do interval wait on success')
40 |
41 | }
42 |
43 | sub waits_ok {
44 | my ($sub, $cmp, $expected_duration, $test_desc) = @_;
45 |
46 | my $start = time;
47 | $sub->();
48 | my $elapsed = time - $start;
49 |
50 | cmp_ok($elapsed, $cmp, $expected_duration, $test_desc);
51 | }
52 |
53 | done_testing;
54 |
--------------------------------------------------------------------------------
/at/chrome.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Selenium::Chrome;
5 | use Selenium::Remote::WDKeys;
6 |
7 | use Cwd qw{abs_path};
8 | use FindBin;
9 |
10 | use Test::More;
11 | use Test::Fatal;
12 | use Test::Deep;
13 |
14 | my $driver;
15 | is( exception { $driver = Selenium::Chrome->new(); }, undef, "can spawn new Selenium::Chrome");
16 |
17 | $driver->debug_on();
18 |
19 | ok($driver->maximize_window(),"can maximize window (WD3)");
20 |
21 | is($driver->get_capabilities()->{browserName},'chrome',"Can get Capabilities correctly (WD3)");
22 | my $sessions = $driver->get_sessions();
23 | is(scalar(@$sessions),1,"Can fall back to selenium2 to list sessions");
24 |
25 | ok($driver->status()->{ready},"status reports OK (WD3)");
26 |
27 | #TODO do something about available_engines
28 |
29 | $driver->set_timeout('page load',10000);
30 | $driver->set_timeout('script',10000);
31 | $driver->set_timeout('implicit',10000);
32 | my $timeouts = $driver->get_timeouts();
33 | is($timeouts->{pageLoad},10000,"WD3 set/get timeouts works");
34 | is($timeouts->{script},10000,"WD3 set/get timeouts works");
35 | is($timeouts->{implicit},10000,"WD3 set/get timeouts works");
36 |
37 | $driver->set_async_script_timeout(20000);
38 | $driver->set_implicit_wait_timeout(5000);
39 | $timeouts = $driver->get_timeouts();
40 | is($timeouts->{script},20000,"WD3 shim for set_async timeouts works");
41 | is($timeouts->{implicit},5000,"WD3 shim for implicit timeouts works");
42 |
43 | my $loc = abs_path("$FindBin::Bin/test.html");
44 | ok($driver->get("file://$loc"),"Can load a web page (WD3)");
45 |
46 | is($driver->get_alert_text(),"BEEE DOOO","get_alert_text works (WD3)");
47 | is(exception { $driver->dismiss_alert() }, undef, "alert can be dismissed (WD3)");
48 |
49 | #This sucker wants "value" instead of "text" like in legacy
50 | ok($driver->send_keys_to_prompt("HORGLE"),"send_keys_to_prompt works (WD3)");
51 | is(exception { $driver->accept_alert() }, undef, "alert can be accepted (WD3)");
52 |
53 | my $handle = $driver->get_current_window_handle();
54 | ok($handle,"Got a window handle (WD3)");
55 | cmp_bag($driver->get_window_handles(),[$handle],"Can list window handles (WD3)");
56 |
57 | my $sz = $driver->get_window_size();
58 | ok(defined $sz->{height},"get_window_size works (WD3)");
59 | ok(defined $sz->{width},"get window size works (WD3)");
60 | my $pos = $driver->get_window_position();
61 | ok(defined $pos->{x},"get_window_size works (WD3)");
62 | ok(defined $pos->{y},"get window size works (WD3)");
63 |
64 | like($driver->get_current_url(),qr/test.html$/,"get_current_url works (WD3)");
65 | like($driver->get_title(),qr/test/i,"get_title works (WD3)");
66 |
67 | my $otherloc = abs_path("$FindBin::Bin/other.html");
68 | $driver->get("file://$otherloc");
69 | $driver->go_back();
70 | $driver->dismiss_alert();
71 | $driver->dismiss_alert();
72 | like($driver->get_title(),qr/test/i,"go_back works (WD3)");
73 |
74 | $driver->go_forward();
75 | like($driver->get_page_source(),qr/ZIPPY/,"go_forward & get_page_source works (WD3)");
76 | is(exception { $driver->refresh() }, undef, "refresh works (WD3)");
77 | $driver->go_back();
78 | $driver->dismiss_alert();
79 | $driver->dismiss_alert();
80 |
81 | #TODO execute_*_script testing
82 |
83 | ok($driver->screenshot(),"can get base64'd whole page screenshot (WD3)");
84 | SKIP: {
85 | skip "chromedriver doesn't know how to take element screenshots", 1;
86 | ok($driver->find_element('body','tag_name')->screenshot(0),"can get element screenshot (WD3 ONLY) and find_element (WD3) works.");
87 | }
88 |
89 | isa_ok($driver->find_element('red','class'),"Selenium::Remote::WebElement");
90 | isa_ok($driver->find_element('text','name'),"Selenium::Remote::WebElement");
91 | isa_ok($driver->find_element('Test Link', 'link_text'),"Selenium::Remote::WebElement");
92 | isa_ok($driver->find_element('Test', 'partial_link_text'),"Selenium::Remote::WebElement");
93 |
94 | is(scalar(@{$driver->find_elements('red','class')}),2,"can find multiple elements correctly");
95 |
96 | my $lem = $driver->find_element('body', 'tag_name');
97 | isa_ok($driver->find_child_element($lem, 'red','class'),"Selenium::Remote::WebElement");
98 | isa_ok($driver->find_child_element($lem, 'text','name'),"Selenium::Remote::WebElement");
99 | isa_ok($driver->find_child_element($lem, 'Test Link', 'link_text'),"Selenium::Remote::WebElement");
100 | isa_ok($driver->find_child_element($lem, 'Test', 'partial_link_text'),"Selenium::Remote::WebElement");
101 |
102 | $lem = $driver->find_element('form','tag_name');
103 | is(scalar(@{$driver->find_child_elements($lem,'./*')}),6,"can find child elements (WD3)");
104 |
105 | isa_ok($driver->get_active_element(),"Selenium::Remote::WebElement");
106 |
107 | is(exception { $driver->cache_status() },undef, "cache_status implemented in krom");
108 | my $coords = {
109 | latitude => 40.714353,
110 | longitude => -74.005973,
111 | };
112 | ok($driver->set_geolocation(location => $coords),"can set_geolocation in krom");
113 | is( $driver->get_geolocation()->{latitude},$coords->{latitude},"cang get_geolocation in krom");
114 |
115 | like($driver->upload_file($otherloc),qr/other.html$/,"upload_file fallback works");
116 |
117 | ok($driver->switch_to_frame($driver->find_element('frame', 'id')),"can switch to frame (WD3)");
118 | ok($driver->switch_to_frame(),"can switch to parent frame (WD3 only)");
119 |
120 | ok($driver->set_window_position(1,1),"can set window position (WD3)");
121 | ok($driver->set_window_size(640,480),"can set window size (WD3)");
122 |
123 | SKIP: {
124 | skip "chromedriver does not minimize", 1;
125 | ok($driver->minimize_window(),"can minimize window (WD3 only)");
126 | }
127 | ok($driver->fullscreen_window(),"can fullscreen window (WD3 only)");
128 |
129 | #XXX chrome has issued a fatwah against cookies issued by file:// urls
130 | is(scalar(@{$driver->get_all_cookies()}),0,"can get cookie list (WD3)");
131 | $driver->delete_all_cookies();
132 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete all cookies (WD3)");
133 |
134 | ok($driver->mouse_move_to_location( element => $driver->find_element('a','tag_name')),"Can use new WD3 Actions API to emulate mouse_move_to_location");
135 | $driver->click();
136 | my $handles = $driver->get_window_handles();
137 | is(scalar(@$handles),2,"Can move to element and then click it correctly (WD3)");
138 |
139 | $driver->switch_to_window($handles->[1]);
140 | is(exception { $driver->close() }, undef, "Can close new window (WD3)");
141 | cmp_bag($driver->get_window_handles,[$handles->[0]],"Correct window closed (WD3)");
142 | $driver->switch_to_window($handles->[0]);
143 |
144 | my $input = $driver->find_element('input','tag_name');
145 | $driver->mouse_move_to_location( element => $input );
146 | $driver->click();
147 | is(exception {$driver->send_modifier('Shift','down')}, undef, "chromedriver can send_modifier");
148 | $driver->send_keys_to_active_element('howdy',KEYS->{tab});
149 | $input->send_keys('eee');
150 | $driver->mouse_move_to_location( element => $driver->find_element('body','tag_name'));
151 | $driver->click();
152 |
153 | #XXX this has to be a BUG in the driver, the keys are getting thru
154 | is($input->get_attribute('value'),'defaultHOWDYEEE',"element->get_attribute() emulates old behavior thru get_property (WD3)");
155 | TODO: {
156 | local $TODO = "chrome can't get_property()";
157 | is($input->get_attribute('value',1),'default',"element->get_attribute() can do it's actual job (WD3)");
158 | }
159 | is($driver->execute_script(qq/ return document.querySelector('input').value /),'defaultHOWDYEEE',"execute_script works, and so does send_keys_to_active_element & element->send_keys (WD3)");
160 | $input->clear();
161 | is($input->get_property('value'),'',"clear() works (WD3)");
162 |
163 | is(exception { $driver->button_down() },undef,"Can button down (WD3)");
164 | is(exception { $driver->button_up() },undef,"Can button up (WD3)");
165 | SKIP: {
166 | skip "chrome don't actionChain", 1;
167 | is(exception { $driver->release_general_action() }, undef, "Can release_general_action (WD3)");
168 | }
169 | ok($driver->find_element('radio2','id')->is_selected(),"WD3 is_selected() works");
170 | my $l1 = $driver->find_element('radio1','id');
171 | $l1->set_selected();
172 | $l1->set_selected();
173 | ok($l1->is_selected(),"WD3 set_selected works");
174 | $l1->toggle();
175 | TODO: {
176 | local $TODO = "chrome toggle won't allow bogus values (radios with both disabled)";
177 | ok(!$l1->is_selected(),"WD3 toggle works: off");
178 | }
179 | $l1->toggle();
180 | ok($l1->is_selected(),"WD3 toggle works: on");
181 |
182 | my $l2 = $driver->find_element('hammertime','id');
183 | is( $l2->is_enabled(),0,"is_enabled works (WD3)");
184 | #ok( $l2->get_element_location()->{x},"Can get element rect (WD3)");
185 | #ok( $l2->get_size()->{'height'}, "Size shim on rect works (WD3)");
186 | is( $l2->get_tag_name(),'input',"get_tag_name works (WD3)");
187 | is( exception { $l2->get_element_location_in_view() }, undef, "get_element_location_in_view available in gegl krom");
188 |
189 | is($driver->find_element('hidon','id')->is_displayed(),0,"is_displayed returns false for type=hidden elements");
190 | my $gone = $driver->find_element('no-see-em','id');
191 | is($gone->is_displayed(),0,"is_displayed returns false for display=none");
192 | is($gone->is_enabled(),1,"is_enabled returns true for non-input elements");
193 |
194 | is($driver->find_element('h1','tag_name')->get_text(),'Howdy Howdy Howdy', "get_text works (WD3)");
195 |
196 | $driver->find_element('clickme','id')->click();
197 | is(exception { $driver->dismiss_alert() }, undef, "Can click element (WD3)");
198 |
199 | $driver->find_element('form','tag_name')->submit();
200 | like($driver->get_page_source(),qr/ZIPPY/,"elem submit() works (WD3)");
201 |
202 | #Pretty sure this one has enough 'inertia' to not disappear all the sudden
203 | $driver->get('http://w3.org/History.html');
204 | $driver->add_cookie('foo','bar','/',undef,0,0,time()+5000);
205 | is(scalar(@{$driver->get_all_cookies()}),1,"can get cookie (WD3)");
206 |
207 | is($driver->get_cookie_named('foo')->{value},'bar',"can get cookie by name (WD3 only)");
208 |
209 | $driver->delete_cookie_named('foo');
210 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete named cookies (WD3)");
211 |
212 | is(exception { $driver->quit() }, undef, "Can quit (WD3)");
213 |
214 | my $port = $driver->port();
215 |
216 | is( exception { $driver->shutdown_binary; }, undef, "can shutdown binary correctly");
217 | sleep 2;
218 |
219 | my $cmd = "lsof -t -i :$port";
220 | my $pid = `$cmd`;
221 | chomp $pid;
222 | is($pid,'',"Destructor appears to have run shutdown_binary and whacked the driver process");
223 |
224 | done_testing();
225 |
226 |
--------------------------------------------------------------------------------
/at/edge.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::More;
5 | use Selenium::Edge;
6 |
7 | my $driver = Selenium::Edge->new();
8 | $driver->get('http://www.perlmonks.org');
9 | like( $driver->get_title(),qr/monastery gates/i,"Can load perlmonks");
10 | $driver->quit();
11 |
12 | done_testing();
13 |
--------------------------------------------------------------------------------
/at/firefox.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Selenium::Firefox;
5 | use Test::More tests => 4;
6 | use Test::Fatal;
7 |
8 | my $driver;
9 | is( exception { $driver = Selenium::Firefox->new(
10 | extra_capabilities => {
11 | 'moz:firefoxOptions' => {
12 | args => [ '-headless' ],
13 | },
14 | },
15 | ); }, undef, "can spawn new Selenium::Firefox");
16 |
17 | my $port = $driver->port();
18 |
19 | ok($driver->get('http://google.com'),"can load a page");
20 | $driver->quit();
21 |
22 | is( exception { $driver->shutdown_binary; }, undef, "can shutdown binary correctly");
23 | sleep 2;
24 |
25 | my $cmd = "lsof -t -i :$port";
26 | my $pid = `$cmd`;
27 | chomp $pid;
28 | is($pid,'',"Destructor appears to have run shutdown_binary and whacked the driver process");
29 |
--------------------------------------------------------------------------------
/at/legacy.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Cwd qw{abs_path};
5 | use FindBin;
6 |
7 | use Test::More;
8 | use Test::Fatal;
9 | use Test::Deep;
10 |
11 | use Selenium::Remote::Driver;
12 | use Selenium::Remote::WDKeys;
13 |
14 | my $driver = Selenium::Remote::Driver->new(
15 | remote_server_addr => '10.17.64.252',
16 | port => 4444,
17 | browser_name => 'firefox',
18 | accept_ssl_certs => 1,
19 | );
20 | isa_ok($driver,'Selenium::Remote::Driver',"Can get new S::R::D with WebDriver3");
21 |
22 | $driver->debug_on();
23 |
24 | is($driver->get_capabilities()->{browserName},'firefox',"Can get Capabilities correctly (WD2)");
25 | my $sessions = $driver->get_sessions();
26 | is(scalar(@$sessions),1,"Can fall back to selenium2 to list sessions");
27 |
28 | ok($driver->status()->{build}->{version},"status reports OK (WD2)");
29 |
30 | #TODO do something about available_engines
31 |
32 | is( exception { $driver->set_timeout('page load',10000) }, undef, "WD2 set_timeout pageload OK");
33 | is( exception { $driver->set_timeout('script',10000) }, undef, "WD2 set_timeout script OK");
34 | is( exception { $driver->set_timeout('implicit',10000) }, undef, "WD2 set_timeout implicit OK");
35 | is( exception { $driver->set_async_script_timeout(20000) }, undef, "WD2 set_async_script_timeout OK");
36 | is( exception { $driver->set_implicit_wait_timeout(5000) }, undef, "WD2 set_implicit_wait_timeout OK");
37 |
38 | my $loc = abs_path("$FindBin::Bin/test.html");
39 | my $local_loc = $driver->upload_file($loc);
40 | note $local_loc;
41 |
42 | ok($driver->get("file://$local_loc"),"Can load a web page (WD2)");
43 |
44 | is($driver->get_alert_text(),"BEEE DOOO","get_alert_text works (WD2)");
45 | is(exception { $driver->dismiss_alert() }, undef, "alert can be dismissed (WD2)");
46 |
47 | #This sucker wants "value" instead of "text" like in legacy
48 | ok($driver->send_keys_to_prompt("HORGLE"),"send_keys_to_prompt works (WD2)");
49 | is(exception { $driver->accept_alert() }, undef, "alert can be accepted (WD2)");
50 |
51 | my $handle = $driver->get_current_window_handle();
52 | ok($handle,"Got a window handle (WD2)");
53 | cmp_bag($driver->get_window_handles(),[$handle],"Can list window handles (WD2)");
54 |
55 | my $sz = $driver->get_window_size();
56 | ok(defined $sz->{height},"get_window_size works (WD2)");
57 | ok(defined $sz->{width},"get window size works (WD2)");
58 | my $pos = $driver->get_window_position();
59 | ok(defined $pos->{x},"get_window_size works (WD2)");
60 | ok(defined $pos->{y},"get window size works (WD2)");
61 |
62 | like($driver->get_current_url(),qr/test.html$/,"get_current_url works (WD2)");
63 | like($driver->get_title(),qr/test/i,"get_title works (WD2)");
64 |
65 | my $otherloc = abs_path("$FindBin::Bin/other.html");
66 | my $other_local_loc = $driver->upload_file($otherloc);
67 | note $other_local_loc;
68 |
69 | $driver->get("file://$other_local_loc");
70 | $driver->go_back();
71 | $driver->dismiss_alert();
72 | $driver->dismiss_alert();
73 | like($driver->get_title(),qr/test/i,"go_back works (WD2)");
74 |
75 | $driver->go_forward();
76 | like($driver->get_page_source(),qr/ZIPPY/,"go_forward & get_page_source works (WD2)");
77 | is(exception { $driver->refresh() }, undef, "refresh works (WD2)");
78 | $driver->go_back();
79 | $driver->dismiss_alert();
80 | $driver->dismiss_alert();
81 |
82 | #TODO execute_*_script testing
83 |
84 | ok($driver->screenshot(),"can get base64'd whole page screenshot (WD2)");
85 |
86 | isa_ok($driver->find_element('red','class'),"Selenium::Remote::WebElement");
87 | isa_ok($driver->find_element('text','name'),"Selenium::Remote::WebElement");
88 | isa_ok($driver->find_element('Test Link', 'link_text'),"Selenium::Remote::WebElement");
89 | isa_ok($driver->find_element('Test', 'partial_link_text'),"Selenium::Remote::WebElement");
90 |
91 | is(scalar(@{$driver->find_elements('red','class')}),2,"can find multiple elements correctly");
92 |
93 | my $lem = $driver->find_element('body', 'tag_name');
94 | isa_ok($driver->find_child_element($lem, 'red','class'),"Selenium::Remote::WebElement");
95 | isa_ok($driver->find_child_element($lem, 'text','name'),"Selenium::Remote::WebElement");
96 | isa_ok($driver->find_child_element($lem, 'Test Link', 'link_text'),"Selenium::Remote::WebElement");
97 | isa_ok($driver->find_child_element($lem, 'Test', 'partial_link_text'),"Selenium::Remote::WebElement");
98 |
99 | $lem = $driver->find_element('form','tag_name');
100 | is(scalar(@{$driver->find_child_elements($lem,'./*')}),6,"can find child elements (WD2)");
101 |
102 | isa_ok($driver->get_active_element(),"Selenium::Remote::WebElement");
103 |
104 | TODO: {
105 | local $TODO = "These methods aren't supported on firefox";
106 | is(exception { $driver->cache_status() },undef, "cache_status works in WD2");
107 | is(exception {
108 | $driver->set_geolocation(location => {
109 | latitude => 40.714353,
110 | longitude => -74.005973,
111 | altitude => 0.056747
112 | });
113 | }, undef, "set_geolocation works in WD2");
114 | is(exception { $driver->get_geolocation() }, undef, "get_geolocation works in WD2");
115 | is(exception { $driver->set_orientation("LANDSCAPE") }, undef, "set_orientation works in WD2");
116 | is(exception { $driver->get_orientation() }, undef, "get_orientation works in WD2");
117 | }
118 |
119 | ok($driver->get_log('server'), "get_log fallback works");
120 | ok( scalar(@{$driver->get_log_types()}),"can fallback for get_log_types");
121 |
122 | like($driver->upload_file($otherloc),qr/other.html$/,"upload_file fallback works");
123 |
124 | #Jinkies, this stuff is cool, it prints the selenium server help page @_@
125 | #diag explain $driver->get_local_storage_item('whee');
126 | #diag explain $driver->delete_local_storage_item('whee');
127 |
128 | ok($driver->switch_to_frame($driver->find_element('frame', 'id')),"can switch to frame (WD2)");
129 | ok($driver->switch_to_frame(),"can switch to parent frame (WD2 only)");
130 |
131 | ok($driver->set_window_position(1,1),"can set window position (WD2)");
132 | ok($driver->set_window_size(200,200),"can set window size (WD2)");
133 |
134 | ok($driver->maximize_window(),"can maximize window (WD2)");
135 |
136 | is(scalar(@{$driver->get_all_cookies()}),1,"can get cookie list (WD2)");
137 | $driver->delete_all_cookies();
138 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete all cookies (WD2)");
139 |
140 | ok($driver->mouse_move_to_location( element => $driver->find_element('a','tag_name')),"Can use mouse_move_to_location");
141 | $driver->click();
142 | my $handles = $driver->get_window_handles();
143 | is(scalar(@$handles),2,"Can move to element and then click it correctly (WD2)");
144 |
145 | $driver->switch_to_window($handles->[1]);
146 | is(exception { $driver->close() }, undef, "Can close new window (WD2)");
147 | cmp_bag($driver->get_window_handles,[$handles->[0]],"Correct window closed (WD2)");
148 | $driver->switch_to_window($handles->[0]);
149 |
150 | my $input = $driver->find_element('input','tag_name');
151 | $driver->mouse_move_to_location( element => $input );
152 | $driver->click();
153 |
154 | #TODO pretty sure this isn't working right
155 | SKIP: {
156 | skip("P.sure send_modifier is kind of screwed up",1);
157 | $driver->send_modifier('Shift','down');
158 | }
159 |
160 | $driver->send_keys_to_active_element('howdy',KEYS->{tab});
161 | $input->send_keys('eee');
162 | $driver->mouse_move_to_location( element => $driver->find_element('body','tag_name'));
163 | $driver->click();
164 |
165 | #XXX this has to be a BUG in the driver, the keys are getting thru
166 | is($input->get_attribute('value'),'defaulthowdyeee',"element->get_attribute() works (WD2)");
167 | is($input->get_attribute('value',1),'defaulthowdyeee',"element->get_attribute() second arg ignored (WD2)");
168 | is($driver->execute_script(qq/ return document.querySelector('input').value /),'defaulthowdyeee',"execute_script works, and so does send_keys_to_active_element & element->send_keys (WD2)");
169 | $input->clear();
170 | is($input->get_attribute('value'),'',"clear() works (WD2)");
171 |
172 | is(exception { $driver->button_down() },undef,"Can button down (WD2)");
173 | is(exception { $driver->button_up() },undef,"Can button up (WD2)");
174 |
175 | ok($driver->find_element('radio2','id')->is_selected(),"WD2 is_selected() works");
176 | my $l1 = $driver->find_element('radio1','id');
177 | SKIP: {
178 | skip "set_selected, toggle_element, is_selected looks broke", 1;
179 | $l1->set_selected();
180 | $l1->set_selected();
181 | ok($l1->is_selected(),"WD2 set_selected works");
182 | $l1->toggle();
183 | ok(!$l1->is_selected(),"WD2 toggle works: off");
184 | $l1->toggle();
185 | ok($l1->is_selected(),"WD2 toggle works: on");
186 | }
187 |
188 | my $l2 = $driver->find_element('hammertime','id');
189 | is( $l2->is_enabled(),0,"is_enabled works (WD2)");
190 | ok( $l2->get_element_location()->{x},"Can get element rect (WD2)");
191 | ok( $l2->get_size()->{'height'}, "Size shim on rect works (WD2)");
192 | is( $l2->get_tag_name(),'input',"get_tag_name works (WD2)");
193 | is( exception { $l2->get_element_location_in_view() }, undef, "get_element_location_in_view not available in WD2");
194 |
195 | is($driver->find_element('hidon','id')->is_displayed(),0,"is_displayed returns false for type=hidden elements");
196 | is($driver->find_element('no-see-em','id')->is_displayed(),0,"is_displayed returns false for display=none");
197 | is($driver->find_element('h1','tag_name')->get_text(),'Howdy Howdy Howdy', "get_text works (WD2)");
198 |
199 | $driver->find_element('clickme','id')->click();
200 | is(exception { $driver->dismiss_alert() }, undef, "Can click element (WD2)");
201 |
202 | $driver->find_element('form','tag_name')->submit();
203 | like($driver->get_page_source(),qr/File not found/,"elem submit() works (WD2)");
204 |
205 | #Pretty sure this one has enough 'inertia' to not disappear all the sudden
206 | $driver->get('http://w3.org/History.html');
207 | $driver->add_cookie('foo','bar',undef,undef,0,0,time()+5000);
208 | is(scalar(@{$driver->get_all_cookies()}),1,"can set cookie (WD2)");
209 |
210 | $driver->delete_cookie_named('foo');
211 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete named cookies (WD2)");
212 |
213 | is(exception { $driver->quit() }, undef, "Can quit (WD2)");
214 |
215 | done_testing();
216 |
--------------------------------------------------------------------------------
/at/other.html:
--------------------------------------------------------------------------------
1 | ZIPPY
2 |
--------------------------------------------------------------------------------
/at/sanity-chrome.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Cwd qw{abs_path};
5 | use FindBin;
6 |
7 | use Test::More;
8 | use Test::Fatal;
9 | use Test::Deep;
10 |
11 | use Selenium::Remote::Driver;
12 | use Selenium::Remote::WDKeys;
13 |
14 | #TODO: cover new_from_caps
15 | #TODO: Selenium::Firefox::Profile usage
16 |
17 | my $driver = Selenium::Remote::Driver->new(
18 | remote_server_addr => 'localhost',
19 | port => 4444,
20 | browser_name => 'chrome',
21 | accept_ssl_certs => 1,
22 | extra_capabilities => {
23 | args => ['start-maximized'],
24 | },
25 | );
26 | isa_ok($driver,'Selenium::Remote::Driver',"Can get new S::R::D with WebDriver3");
27 |
28 | $driver->debug_on();
29 |
30 | ok($driver->maximize_window(),"can maximize window (WD3)");
31 |
32 | is($driver->get_capabilities()->{browserName},'chrome',"Can get Capabilities correctly (WD3)");
33 | my $sessions = $driver->get_sessions();
34 | is(scalar(@$sessions),1,"Can fall back to selenium2 to list sessions");
35 |
36 | ok($driver->status()->{ready},"status reports OK (WD3)");
37 |
38 | #TODO do something about available_engines
39 |
40 | $driver->set_timeout('page load',10000);
41 | $driver->set_timeout('script',10000);
42 | $driver->set_timeout('implicit',10000);
43 | my $timeouts = $driver->get_timeouts();
44 | is($timeouts->{pageLoad},10000,"WD3 set/get timeouts works");
45 | is($timeouts->{script},10000,"WD3 set/get timeouts works");
46 | is($timeouts->{implicit},10000,"WD3 set/get timeouts works");
47 |
48 | $driver->set_async_script_timeout(20000);
49 | $driver->set_implicit_wait_timeout(5000);
50 | $timeouts = $driver->get_timeouts();
51 | is($timeouts->{script},20000,"WD3 shim for set_async timeouts works");
52 | is($timeouts->{implicit},5000,"WD3 shim for implicit timeouts works");
53 |
54 | my $loc = abs_path("$FindBin::Bin/test.html");
55 | ok($driver->get("file://$loc"),"Can load a web page (WD3)");
56 |
57 | is($driver->get_alert_text(),"BEEE DOOO","get_alert_text works (WD3)");
58 | is(exception { $driver->dismiss_alert() }, undef, "alert can be dismissed (WD3)");
59 |
60 | #This sucker wants "value" instead of "text" like in legacy
61 | ok($driver->send_keys_to_prompt("HORGLE"),"send_keys_to_prompt works (WD3)");
62 | is(exception { $driver->accept_alert() }, undef, "alert can be accepted (WD3)");
63 |
64 | my $handle = $driver->get_current_window_handle();
65 | ok($handle,"Got a window handle (WD3)");
66 | cmp_bag($driver->get_window_handles(),[$handle],"Can list window handles (WD3)");
67 |
68 | my $sz = $driver->get_window_size();
69 | ok(defined $sz->{height},"get_window_size works (WD3)");
70 | ok(defined $sz->{width},"get window size works (WD3)");
71 | my $pos = $driver->get_window_position();
72 | ok(defined $pos->{x},"get_window_size works (WD3)");
73 | ok(defined $pos->{y},"get window size works (WD3)");
74 |
75 | like($driver->get_current_url(),qr/test.html$/,"get_current_url works (WD3)");
76 | like($driver->get_title(),qr/test/i,"get_title works (WD3)");
77 |
78 | my $otherloc = abs_path("$FindBin::Bin/other.html");
79 | $driver->get("file://$otherloc");
80 | $driver->go_back();
81 | $driver->dismiss_alert();
82 | $driver->dismiss_alert();
83 | like($driver->get_title(),qr/test/i,"go_back works (WD3)");
84 |
85 | $driver->go_forward();
86 | like($driver->get_page_source(),qr/ZIPPY/,"go_forward & get_page_source works (WD3)");
87 | is(exception { $driver->refresh() }, undef, "refresh works (WD3)");
88 | $driver->go_back();
89 | $driver->dismiss_alert();
90 | $driver->dismiss_alert();
91 |
92 | #TODO execute_*_script testing
93 |
94 | ok($driver->screenshot(),"can get base64'd whole page screenshot (WD3)");
95 | SKIP: {
96 | skip "chromedriver doesn't know how to take element screenshots", 1;
97 | ok($driver->find_element('body','tag_name')->screenshot(0),"can get element screenshot (WD3 ONLY) and find_element (WD3) works.");
98 | }
99 |
100 | isa_ok($driver->find_element('red','class'),"Selenium::Remote::WebElement");
101 | isa_ok($driver->find_element('text','name'),"Selenium::Remote::WebElement");
102 | isa_ok($driver->find_element('Test Link', 'link_text'),"Selenium::Remote::WebElement");
103 | isa_ok($driver->find_element('Test', 'partial_link_text'),"Selenium::Remote::WebElement");
104 |
105 | is(scalar(@{$driver->find_elements('red','class')}),2,"can find multiple elements correctly");
106 |
107 | my $lem = $driver->find_element('body', 'tag_name');
108 | isa_ok($driver->find_child_element($lem, 'red','class'),"Selenium::Remote::WebElement");
109 | isa_ok($driver->find_child_element($lem, 'text','name'),"Selenium::Remote::WebElement");
110 | isa_ok($driver->find_child_element($lem, 'Test Link', 'link_text'),"Selenium::Remote::WebElement");
111 | isa_ok($driver->find_child_element($lem, 'Test', 'partial_link_text'),"Selenium::Remote::WebElement");
112 |
113 | $lem = $driver->find_element('form','tag_name');
114 | is(scalar(@{$driver->find_child_elements($lem,'./*')}),6,"can find child elements (WD3)");
115 |
116 | isa_ok($driver->get_active_element(),"Selenium::Remote::WebElement");
117 |
118 | is(exception { $driver->cache_status() },undef, "cache_status implemented in krom");
119 | my $coords = {
120 | latitude => 40.714353,
121 | longitude => -74.005973,
122 | };
123 | ok($driver->set_geolocation(location => $coords),"can set_geolocation in krom");
124 | is( $driver->get_geolocation()->{latitude},$coords->{latitude},"cang get_geolocation in krom");
125 |
126 | ok($driver->get_log('server'), "get_log fallback works");
127 | ok( scalar(@{$driver->get_log_types()}),"can fallback for get_log_types");
128 |
129 | isnt(exception { $driver->set_orientation("LANDSCAPE") },undef,"set_orientation unavailable on desktop");
130 | like(exception { $driver->get_orientation() },qr/error/,"get_orientation unavailable on desktop");
131 |
132 | like($driver->upload_file($otherloc),qr/other.html$/,"upload_file fallback works");
133 |
134 | #Jinkies, this stuff is cool, it prints the selenium server help page @_@
135 | like( exception { $driver->get_local_storage_item('whee') },qr/help/i,"get_local_storage_item prints help page");
136 | like( exception { $driver->delete_local_storage_item('whee') },qr/help/i,"get_local_storage_item prints help page");
137 |
138 | ok($driver->switch_to_frame($driver->find_element('frame', 'id')),"can switch to frame (WD3)");
139 | ok($driver->switch_to_frame(),"can switch to parent frame (WD3 only)");
140 |
141 | ok($driver->set_window_position(1,1),"can set window position (WD3)");
142 | ok($driver->set_window_size(640,480),"can set window size (WD3)");
143 |
144 | SKIP: {
145 | skip "chromedriver does not minimize", 1;
146 | ok($driver->minimize_window(),"can minimize window (WD3 only)");
147 | }
148 | ok($driver->fullscreen_window(),"can fullscreen window (WD3 only)");
149 |
150 | #XXX chrome has issued a fatwah against cookies issued by file:// urls
151 | is(scalar(@{$driver->get_all_cookies()}),0,"can get cookie list (WD3)");
152 | $driver->delete_all_cookies();
153 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete all cookies (WD3)");
154 |
155 | ok($driver->mouse_move_to_location( element => $driver->find_element('a','tag_name')),"Can use new WD3 Actions API to emulate mouse_move_to_location");
156 | $driver->click();
157 | my $handles = $driver->get_window_handles();
158 | is(scalar(@$handles),2,"Can move to element and then click it correctly (WD3)");
159 |
160 | $driver->switch_to_window($handles->[1]);
161 | is(exception { $driver->close() }, undef, "Can close new window (WD3)");
162 | cmp_bag($driver->get_window_handles,[$handles->[0]],"Correct window closed (WD3)");
163 | $driver->switch_to_window($handles->[0]);
164 |
165 | my $input = $driver->find_element('input','tag_name');
166 | $driver->mouse_move_to_location( element => $input );
167 | $driver->click();
168 | isnt(exception {$driver->send_modifier('Shift','down')}, undef, "chromedriver can't send_modifier");
169 | $driver->send_keys_to_active_element('howdy',KEYS->{tab});
170 | $input->send_keys('eee');
171 | $driver->mouse_move_to_location( element => $driver->find_element('body','tag_name'));
172 | $driver->click();
173 |
174 | #XXX this has to be a BUG in the driver, the keys are getting thru
175 | is($input->get_attribute('value'),'defaulthowdyeee',"element->get_attribute() emulates old behavior thru get_property (WD3)");
176 | TODO: {
177 | local $TODO = "chrome can't get_property()";
178 | is($input->get_attribute('value',1),'default',"element->get_attribute() can do it's actual job (WD3)");
179 | }
180 | is($driver->execute_script(qq/ return document.querySelector('input').value /),'defaulthowdyeee',"execute_script works, and so does send_keys_to_active_element & element->send_keys (WD3)");
181 | $input->clear();
182 | is($input->get_property('value'),'',"clear() works (WD3)");
183 |
184 | is(exception { $driver->button_down() },undef,"Can button down (WD3)");
185 | is(exception { $driver->button_up() },undef,"Can button up (WD3)");
186 | SKIP: {
187 | skip "chrome don't actionChain", 1;
188 | is(exception { $driver->release_general_action() }, undef, "Can release_general_action (WD3)");
189 | }
190 | ok($driver->find_element('radio2','id')->is_selected(),"WD3 is_selected() works");
191 | my $l1 = $driver->find_element('radio1','id');
192 | $l1->set_selected();
193 | $l1->set_selected();
194 | ok($l1->is_selected(),"WD3 set_selected works");
195 | $l1->toggle();
196 | TODO: {
197 | local $TODO = "chrome toggle won't allow bogus values (radios with both disabled)";
198 | ok(!$l1->is_selected(),"WD3 toggle works: off");
199 | }
200 | $l1->toggle();
201 | ok($l1->is_selected(),"WD3 toggle works: on");
202 |
203 | my $l2 = $driver->find_element('hammertime','id');
204 | is( $l2->is_enabled(),0,"is_enabled works (WD3)");
205 | ok( $l2->get_element_location()->{x},"Can get element rect (WD3)");
206 | ok( $l2->get_size()->{'height'}, "Size shim on rect works (WD3)");
207 | is( $l2->get_tag_name(),'input',"get_tag_name works (WD3)");
208 | is( exception { $l2->get_element_location_in_view() }, undef, "get_element_location_in_view available in gegl krom");
209 |
210 | is($driver->find_element('hidon','id')->is_displayed(),0,"is_displayed returns false for type=hidden elements");
211 | my $gone = $driver->find_element('no-see-em','id');
212 | is($gone->is_displayed(),0,"is_displayed returns false for display=none");
213 | is($gone->is_enabled(),1,"is_enabled returns true for non-input elements");
214 |
215 | is($driver->find_element('h1','tag_name')->get_text(),'Howdy Howdy Howdy', "get_text works (WD3)");
216 |
217 | $driver->find_element('clickme','id')->click();
218 | is(exception { $driver->dismiss_alert() }, undef, "Can click element (WD3)");
219 |
220 | $driver->find_element('form','tag_name')->submit();
221 | like($driver->get_page_source(),qr/ZIPPY/,"elem submit() works (WD3)");
222 |
223 | #Pretty sure this one has enough 'inertia' to not disappear all the sudden
224 | $driver->get('http://w3.org/History.html');
225 | $driver->add_cookie('foo','bar',undef,undef,0,0,time()+5000);
226 | is(scalar(@{$driver->get_all_cookies()}),1,"can set cookie (WD3)");
227 |
228 | is($driver->get_cookie_named('foo')->{value},'bar',"can get cookie by name (WD3 only)");
229 |
230 | $driver->delete_cookie_named('foo');
231 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete named cookies (WD3)");
232 |
233 | is(exception { $driver->quit() }, undef, "Can quit (WD3)");
234 |
235 | done_testing();
236 |
--------------------------------------------------------------------------------
/at/sanity.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Cwd qw{abs_path};
5 | use FindBin;
6 |
7 | use Test::More;
8 | use Test::Fatal;
9 | use Test::Deep;
10 |
11 | use Selenium::Remote::Driver;
12 | use Selenium::Remote::WDKeys;
13 |
14 | #TODO: cover new_from_caps
15 | #TODO: Selenium::Firefox::Profile usage
16 |
17 | $Selenium::Remote::Driver::FORCE_WD3 = 1;
18 | my $driver = Selenium::Remote::Driver->new(
19 | remote_server_addr => 'localhost',
20 | port => 4444,
21 | browser_name => 'firefox',
22 | accept_ssl_certs => 1,
23 | extra_capabilities => {
24 | log => { level => 'trace' },
25 | },
26 | );
27 | isa_ok($driver,'Selenium::Remote::Driver',"Can get new S::R::D with WebDriver3");
28 |
29 | $driver->debug_on();
30 |
31 | is($driver->get_capabilities()->{browserName},'firefox',"Can get Capabilities correctly (WD3)");
32 | my $sessions = $driver->get_sessions();
33 | is(scalar(@$sessions),1,"Can fall back to selenium2 to list sessions");
34 |
35 | ok($driver->status()->{ready},"status reports OK (WD3)");
36 |
37 | #TODO do something about available_engines
38 |
39 | $driver->set_timeout('page load',10000);
40 | $driver->set_timeout('script',10000);
41 | $driver->set_timeout('implicit',10000);
42 | my $timeouts = $driver->get_timeouts();
43 | is($timeouts->{pageLoad},10000,"WD3 set/get timeouts works");
44 | is($timeouts->{script},10000,"WD3 set/get timeouts works");
45 | is($timeouts->{implicit},10000,"WD3 set/get timeouts works");
46 |
47 | $driver->set_async_script_timeout(20000);
48 | $driver->set_implicit_wait_timeout(5000);
49 | $timeouts = $driver->get_timeouts();
50 | is($timeouts->{script},20000,"WD3 shim for set_async timeouts works");
51 | is($timeouts->{implicit},5000,"WD3 shim for implicit timeouts works");
52 |
53 | my $loc = abs_path("$FindBin::Bin/test.html");
54 | ok($driver->get("file://$loc"),"Can load a web page (WD3)");
55 |
56 | is($driver->get_alert_text(),"BEEE DOOO","get_alert_text works (WD3)");
57 | is(exception { $driver->dismiss_alert() }, undef, "alert can be dismissed (WD3)");
58 |
59 | #This sucker wants "value" instead of "text" like in legacy
60 | ok($driver->send_keys_to_prompt("HORGLE"),"send_keys_to_prompt works (WD3)");
61 | is(exception { $driver->accept_alert() }, undef, "alert can be accepted (WD3)");
62 |
63 | my $handle = $driver->get_current_window_handle();
64 | ok($handle,"Got a window handle (WD3)");
65 | cmp_bag($driver->get_window_handles(),[$handle],"Can list window handles (WD3)");
66 |
67 | my $sz = $driver->get_window_size();
68 | ok(defined $sz->{height},"get_window_size works (WD3)");
69 | ok(defined $sz->{width},"get window size works (WD3)");
70 | my $pos = $driver->get_window_position();
71 | ok(defined $pos->{x},"get_window_size works (WD3)");
72 | ok(defined $pos->{y},"get window size works (WD3)");
73 |
74 | like($driver->get_current_url(),qr/test.html$/,"get_current_url works (WD3)");
75 | like($driver->get_title(),qr/test/i,"get_title works (WD3)");
76 |
77 | my $otherloc = abs_path("$FindBin::Bin/other.html");
78 | $driver->get("file://$otherloc");
79 | $driver->go_back();
80 | like($driver->get_title(),qr/test/i,"go_back works (WD3)");
81 |
82 | $driver->go_forward();
83 | like($driver->get_page_source(),qr/ZIPPY/,"go_forward & get_page_source works (WD3)");
84 | is(exception { $driver->refresh() }, undef, "refresh works (WD3)");
85 | $driver->go_back();
86 |
87 | #TODO execute_*_script testing
88 |
89 | ok($driver->screenshot(),"can get base64'd whole page screenshot (WD3)");
90 | ok($driver->find_element('body','tag_name')->screenshot(0),"can get element screenshot (WD3 ONLY) and find_element (WD3) works.");
91 |
92 | isa_ok($driver->find_element('red','class'),"Selenium::Remote::WebElement");
93 | isa_ok($driver->find_element('text','name'),"Selenium::Remote::WebElement");
94 | isa_ok($driver->find_element('Test Link', 'link_text'),"Selenium::Remote::WebElement");
95 | isa_ok($driver->find_element('Test', 'partial_link_text'),"Selenium::Remote::WebElement");
96 |
97 | is(scalar(@{$driver->find_elements('red','class')}),2,"can find multiple elements correctly");
98 |
99 | my $lem = $driver->find_element('body', 'tag_name');
100 | isa_ok($driver->find_child_element($lem, 'red','class'),"Selenium::Remote::WebElement");
101 | isa_ok($lem->child('red','class'),"Selenium::Remote::WebElement");
102 | isa_ok($driver->find_child_element($lem, 'text','name'),"Selenium::Remote::WebElement");
103 | isa_ok($driver->find_child_element($lem, 'Test Link', 'link_text'),"Selenium::Remote::WebElement");
104 | isa_ok($driver->find_child_element($lem, 'Test', 'partial_link_text'),"Selenium::Remote::WebElement");
105 |
106 | $lem = $driver->find_element('form','tag_name');
107 | is(scalar(@{$driver->find_child_elements($lem,'./*')}),6,"can find child elements (WD3)");
108 | is(scalar(@{$lem->children('./*')}),6,"can find child elements via children() alias (WD3)");
109 |
110 | isa_ok($driver->get_active_element(),"Selenium::Remote::WebElement");
111 |
112 | like(exception { $driver->cache_status() },qr/unknown command/, "cache_status unimplemented in WD3");
113 | like(exception {
114 | diag explain $driver->set_geolocation(location => {
115 | latitude => 40.714353,
116 | longitude => -74.005973,
117 | altitude => 0.056747
118 | });
119 | }, qr/unknown command/, "set_geolocation unimplemented in WD3");
120 | like(exception { $driver->get_geolocation() }, qr/unknown command/, "get_geolocation unimplemented in WD3");
121 |
122 | ok($driver->get_log('server'), "get_log fallback works");
123 | ok( scalar(@{$driver->get_log_types()}),"can fallback for get_log_types");
124 |
125 | like(exception { $driver->set_orientation("LANDSCAPE") }, qr/unknown command/, "set_orientation unimplemented in WD3");
126 | like(exception { $driver->get_orientation() }, qr/unknown command/, "get_orientation unimplemented in WD3");
127 |
128 | like($driver->upload_file($otherloc),qr/other.html$/,"upload_file fallback works");
129 |
130 | #Jinkies, this stuff is cool, it prints the selenium server help page @_@
131 | like( exception { $driver->get_local_storage_item('whee') },qr/help/i,"get_local_storage_item prints help page");
132 | like( exception { $driver->delete_local_storage_item('whee') },qr/help/i,"get_local_storage_item prints help page");
133 |
134 | ok($driver->switch_to_frame($driver->find_element('frame', 'id')),"can switch to frame (WD3)");
135 | ok($driver->switch_to_frame(),"can switch to parent frame (WD3 only)");
136 |
137 | ok($driver->set_window_position(1,1),"can set window position (WD3)");
138 | ok($driver->set_window_size(640,480),"can set window size (WD3)");
139 |
140 | SKIP: {
141 | skip(2, "maxi/mini not working right now?");
142 | ok($driver->maximize_window(),"can maximize window (WD3)");
143 | ok($driver->minimize_window(),"can minimize window (WD3 only)");
144 | }
145 | ok($driver->fullscreen_window(),"can fullscreen window (WD3 only)");
146 |
147 | is(scalar(@{$driver->get_all_cookies()}),1,"can get cookie list (WD3)");
148 | $driver->delete_all_cookies();
149 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete all cookies (WD3)");
150 |
151 | ok($driver->mouse_move_to_location( element => $driver->find_element('a','tag_name')),"Can use new WD3 Actions API to emulate mouse_move_to_location");
152 | $driver->click();
153 | sleep 5;
154 | my $handles = $driver->get_window_handles();
155 | is(scalar(@$handles),2,"Can move to element and then click it correctly (WD3)");
156 |
157 | $driver->switch_to_window($handles->[1]);
158 | is(exception { $driver->close() }, undef, "Can close new window (WD3)");
159 | cmp_bag($driver->get_window_handles,[$handles->[0]],"Correct window closed (WD3)");
160 | $driver->switch_to_window($handles->[0]);
161 |
162 | my $input = $driver->find_element('input','tag_name');
163 | $driver->mouse_move_to_location( element => $input );
164 | $driver->click();
165 | #TODO pretty sure this isn't working right
166 | $driver->send_modifier('Shift','down');
167 | $driver->send_keys_to_active_element('howdy',KEYS->{tab});
168 | $input->send_keys('eee');
169 | $driver->mouse_move_to_location( element => $driver->find_element('body','tag_name'));
170 | $driver->click();
171 |
172 | #XXX this has to be a BUG in the driver, the keys are getting thru
173 | is($input->get_attribute('value'),'defaulthowdyeee',"element->get_attribute() emulates old behavior thru get_property (WD3)");
174 | is($input->get_attribute('value',1),'default',"element->get_attribute() can do it's actual job (WD3)");
175 | is($driver->execute_script(qq/ return document.querySelector('input').value /),'defaulthowdyeee',"execute_script works, and so does send_keys_to_active_element & element->send_keys (WD3)");
176 | $input->clear();
177 | is($input->get_property('value'),'',"clear() works (WD3)");
178 |
179 | is(exception { $driver->button_down() },undef,"Can button down (WD3)");
180 | is(exception { $driver->button_up() },undef,"Can button up (WD3)");
181 | is(exception { $driver->release_general_action() }, undef, "Can release_general_action (WD3)");
182 |
183 | ok($driver->find_element('radio2','id')->is_selected(),"WD3 is_selected() works");
184 | my $l1 = $driver->find_element('radio1','id');
185 | $l1->set_selected();
186 | $l1->set_selected();
187 | ok($l1->is_selected(),"WD3 set_selected works");
188 | $l1->toggle();
189 | ok(!$l1->is_selected(),"WD3 toggle works: off");
190 | $l1->toggle();
191 | ok($l1->is_selected(),"WD3 toggle works: on");
192 |
193 | my $l2 = $driver->find_element('hammertime','id');
194 | is( $l2->is_enabled(),0,"is_enabled works (WD3)");
195 | ok( $l2->get_element_location()->{x},"Can get element rect (WD3)");
196 | ok( $l2->get_size()->{'height'}, "Size shim on rect works (WD3)");
197 | is( $l2->get_tag_name(),'input',"get_tag_name works (WD3)");
198 | ok( defined $l2->get_element_location_in_view()->{x}, "get_element_location_in_view polyfill works (WD3)");
199 |
200 | is($driver->find_element('hidon','id')->is_displayed(),0,"is_displayed returns false for type=hidden elements");
201 | my $gone = $driver->find_element('no-see-em','id');
202 | is($gone->is_displayed(),0,"is_displayed returns false for display=none");
203 | is($gone->is_enabled(),1,"is_enabled returns true for non-input elements");
204 |
205 | is($driver->find_element('h1','tag_name')->get_text(),'Howdy Howdy Howdy', "get_text works (WD3)");
206 |
207 | $driver->find_element('clickme','id')->click();
208 | is(exception { $driver->dismiss_alert() }, undef, "Can click element (WD3)");
209 |
210 | $driver->find_element('form','tag_name')->submit();
211 | like($driver->get_page_source(),qr/ZIPPY/,"elem submit() works (WD3)");
212 |
213 | #Pretty sure this one has enough 'inertia' to not disappear all the sudden
214 | $driver->get('http://w3.org/History.html');
215 | $driver->add_cookie('foo','bar',undef,undef,0,0,time()+5000);
216 | is(scalar(@{$driver->get_all_cookies()}),1,"can set cookie (WD3)");
217 |
218 | is($driver->get_cookie_named('foo')->{value},'bar',"can get cookie by name (WD3 only)");
219 |
220 | $driver->delete_cookie_named('foo');
221 | is(scalar(@{$driver->get_all_cookies()}),0,"can delete named cookies (WD3)");
222 |
223 | is(exception { $driver->quit() }, undef, "Can quit (WD3)");
224 |
225 | done_testing();
226 |
--------------------------------------------------------------------------------
/at/test-firefox.test:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::Selenium::Firefox;
5 | use Test::More tests => 3;
6 | use Test::Fatal;
7 |
8 | my $driver;
9 | is( exception { $driver = Test::Selenium::Firefox->new(
10 | extra_capabilities => {
11 | 'moz:firefoxOptions' => {
12 | args => [ '-headless' ],
13 | },
14 | },
15 | ); }, undef, "can spawn new Selenium::Firefox");
16 |
17 | $driver->get('http://google.com');
18 |
19 | TODO: {
20 | local $TODO = "This test must fail";
21 | $driver->click_ok('not_here','css',"click on non-existant element doesn't croak");
22 | };
23 |
24 |
25 | is( exception { $driver->shutdown_binary; }, undef, "can shutdown binary correctly");
26 |
27 |
--------------------------------------------------------------------------------
/at/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Page
6 |
11 |
17 |
18 |
19 |
20 |
21 | Howdy Howdy Howdy
22 |
23 |
31 |
32 | Tickle
33 | PARTY HARD
34 |
35 | Test Link
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/cpanfile:
--------------------------------------------------------------------------------
1 | requires "Archive::Zip" => "0";
2 | requires "Carp" => "0";
3 | requires "Cwd" => "0";
4 | requires "Data::Dumper" => "0";
5 | requires "Exporter" => "0";
6 | requires "File::Basename" => "0";
7 | requires "File::Copy" => "0";
8 | requires "File::Spec" => "0";
9 | requires "File::Spec::Functions" => "0";
10 | requires "File::Temp" => "0";
11 | requires "File::Which" => "0";
12 | requires "HTTP::Headers" => "0";
13 | requires "HTTP::Request" => "0";
14 | requires "HTTP::Response" => "0";
15 | requires "IO::Socket" => "0";
16 | requires "IO::Socket::INET" => "0";
17 | requires "IO::String" => "0";
18 | requires "IO::Uncompress::Unzip" => "2.030";
19 | requires "JSON" => "0";
20 | requires "LWP::UserAgent" => "0";
21 | requires "List::Util" => "1.33";
22 | requires "MIME::Base64" => "0";
23 | requires "Moo" => "1.005";
24 | requires "Moo::Role" => "0";
25 | requires "Scalar::Util" => "0";
26 | requires "Sub::Install" => "0";
27 | requires "Test::Builder" => "0";
28 | requires "Test::LongString" => "0";
29 | requires "Time::HiRes" => "0";
30 | requires "Try::Tiny" => "0";
31 | requires "XML::Simple" => "0";
32 | requires "base" => "0";
33 | requires "constant" => "0";
34 | requires "namespace::clean" => "0";
35 | requires "perl" => "5.010";
36 | requires "strict" => "0";
37 | requires "warnings" => "0";
38 |
39 | on 'test' => sub {
40 | requires "File::Spec" => "0";
41 | requires "File::stat" => "0";
42 | requires "FindBin" => "0";
43 | requires "IO::Handle" => "0";
44 | requires "IPC::Open3" => "0";
45 | requires "Test::Builder::Tester" => "0";
46 | requires "Test::Fatal" => "0";
47 | requires "Test::LWP::UserAgent" => "0";
48 | requires "Test::More" => "0";
49 | requires "Test::Time" => "0";
50 | requires "Test::Warn" => "0";
51 | requires "blib" => "1.01";
52 | requires "lib" => "0";
53 | requires "Test::MockModule" => "0.176";
54 | };
55 |
56 | on 'configure' => sub {
57 | requires "ExtUtils::MakeMaker" => "0";
58 | };
59 |
60 | on 'develop' => sub {
61 | requires "Pod::Coverage::TrustPod" => "0";
62 | requires "Test::CPAN::Changes" => "0.19";
63 | requires "Test::CPAN::Meta" => "0";
64 | requires "Test::CPAN::Meta::JSON" => "0.16";
65 | requires "Test::EOL" => "0";
66 | requires "Test::Kwalitee" => "1.21";
67 | requires "Test::Mojibake" => "0";
68 | requires "Test::More" => "0.88";
69 | requires "Test::NoTabs" => "0";
70 | requires "Test::Pod" => "1.41";
71 | requires "Test::Pod::Coverage" => "1.08";
72 | requires "Test::Pod::LinkCheck" => "0";
73 | requires "Test::Portability::Files" => "0";
74 | requires "Test::Synopsis" => "0";
75 | requires "Test::Version" => "1";
76 | };
77 |
--------------------------------------------------------------------------------
/dist.ini:
--------------------------------------------------------------------------------
1 | name = Selenium-Remote-Driver
2 | version = 1.49
3 | author = George S. Baugh
4 | author = Aditya Ivaturi
5 | author = Daniel Gempesaw
6 | author = Emmanuel Peroumalnaïk
7 | author = Luke Closs
8 | author = Mark Stosberg
9 | license = Apache_2_0
10 | copyright_holder = George S. Baugh
11 | copyright_year = 2018
12 |
13 | [GatherDir]
14 | include_dotfiles = 1
15 | exclude_match = .*\.swp
16 | exclude_match = .*\.swo
17 |
18 | [PruneCruft]
19 | except = \.travis.yml
20 |
21 | [ManifestSkip]
22 | [MetaYAML]
23 | [MetaJSON]
24 | [License]
25 | [Readme]
26 | [ExtraTests]
27 | [ExecDir]
28 | [ShareDir]
29 | [MakeMaker]
30 | [Manifest]
31 |
32 | [PkgVersion]
33 | [AutoPrereqs]
34 | [MetaProvides::Package]
35 |
36 | [PodWeaver]
37 | finder = :NotTestSRD
38 |
39 | ; skip the Test::SRD modules, their attribution is non-standard
40 | [FileFinder::ByName / :NotTestSRD]
41 | dir = lib
42 | match = \.pm$
43 | skip = Test
44 |
45 | [Git::Contributors]
46 |
47 | ; XXX can't tidy this mess yet
48 | ;[TidyAll]
49 |
50 | ; Unfortunately CPAN changes detects the first date incorrectly. Oh well...
51 | ; Unfortunately the Manifest test does not work for unknown reasons.
52 | [@TestingMania]
53 | critic_config = perlcriticrc
54 | disable = Test::CPAN::Changes
55 | disable = Test::DistManifest
56 |
57 | [TestRelease]
58 | [ConfirmRelease]
59 | [UploadToCPAN]
60 |
61 | [CheckMetaResources]
62 | [CheckPrereqsIndexed]
63 | [CheckChangesHasContent]
64 |
65 | [Prereqs / RuntimeRequires]
66 | perl = 5.010
67 | Moo = 1.005
68 | List::Util = 1.33
69 |
70 | [GithubMeta]
71 | issues = 1
72 | user = teodesian
73 |
74 | [Encoding]
75 | filename = t/www/icon.gif
76 | filename = t/www/invalid-extension.xpi
77 | filename = t/www/redisplay.xpi
78 | encoding = bytes
79 |
80 | ; `dzil authordeps` doesn't know about the Pod Weaver dependenciess:
81 | ; authordep Pod::Weaver::Section::Contributors = 0
82 | ; authordep Pod::Weaver::Plugin::Encoding = 0
83 | ; authordep Pod::Weaver::Section::SeeAlso = 0
84 | ; authordep Pod::Weaver::Section::GenerateSection = 0
85 | ; authordep Pod::Elemental::Transformer::List = 0
86 | ; authordep XML::Simple = 0
87 | ; authordep Test::LWP::UserAgent = 0
88 | ; authordep Test::Pod::Coverage = 0
89 | ; authordep Term::UI = 0
90 |
--------------------------------------------------------------------------------
/driver-example.pl:
--------------------------------------------------------------------------------
1 | #!/bin/env perl
2 | use Selenium::Remote::Driver;
3 | use Test::More tests=>4;
4 |
5 | my $driver = Selenium::Remote::Driver->new;
6 | $driver->get("http://www.google.com");
7 | $driver->find_element('q','name')->send_keys("Hello WebDriver!");
8 |
9 | ok($driver->get_title =~ /Google/,"title matches google");
10 | is($driver->get_title,'Google',"Title is google");
11 | ok($driver->get_title eq 'Google','Title equals google');
12 | like($driver->get_title,qr/Google/,"Title matches google");
13 |
14 | $driver->quit();
15 |
--------------------------------------------------------------------------------
/lib/Selenium/CanStartBinary/FindBinary.pm:
--------------------------------------------------------------------------------
1 | package Selenium::CanStartBinary::FindBinary;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Coercions for finding webdriver binaries on your system
7 | use Cwd qw/abs_path/;
8 | use File::Which qw/which/;
9 | use IO::Socket::INET;
10 | use Selenium::Firefox::Binary qw/firefox_path/;
11 |
12 | require Exporter;
13 | our @ISA = qw/Exporter/;
14 | our @EXPORT_OK = qw/coerce_simple_binary coerce_firefox_binary/;
15 |
16 | use constant IS_WIN => $^O eq 'MSWin32';
17 |
18 | =for Pod::Coverage *EVERYTHING*
19 |
20 | =cut
21 |
22 | sub coerce_simple_binary {
23 | my ($executable) = @_;
24 |
25 | my $manual_binary = _validate_manual_binary($executable);
26 | if ($manual_binary) {
27 | return $manual_binary;
28 | }
29 | else {
30 | return _naive_find_binary($executable);
31 | }
32 | }
33 |
34 | sub coerce_firefox_binary {
35 | my ($executable) = @_;
36 |
37 | my $manual_binary = _validate_manual_binary($executable);
38 | if ($manual_binary) {
39 | return $manual_binary;
40 | }
41 | else {
42 | return firefox_path();
43 | }
44 | }
45 |
46 | sub _validate_manual_binary {
47 | my ($executable) = @_;
48 |
49 | my $abs_executable = eval {
50 | my $path = abs_path($executable);
51 | die if $path && !-f $path;
52 | $path;
53 | };
54 |
55 | if ($abs_executable) {
56 | if ( -x $abs_executable || IS_WIN ) {
57 | return $abs_executable;
58 | }
59 | else {
60 | die 'The binary at '
61 | . $executable
62 | . ' is not executable. Choose the correct file or chmod +x it as needed.';
63 | }
64 | }
65 | }
66 |
67 | sub _naive_find_binary {
68 | my ($executable) = @_;
69 |
70 | my $naive_binary = which($executable);
71 | if ( defined $naive_binary ) {
72 | return $naive_binary;
73 | }
74 | else {
75 | warn qq(Unable to find the $executable binary in your \$PATH.);
76 | return;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/Selenium/CanStartBinary/ProbePort.pm:
--------------------------------------------------------------------------------
1 | package Selenium::CanStartBinary::ProbePort;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Utility functions for finding open ports to eventually bind to
7 |
8 | use IO::Socket::INET;
9 | use Selenium::Waiter qw/wait_until/;
10 |
11 | require Exporter;
12 | our @ISA = qw/Exporter/;
13 | our @EXPORT_OK = qw/find_open_port_above find_open_port probe_port/;
14 |
15 | =for Pod::Coverage *EVERYTHING*
16 |
17 | =cut
18 |
19 | sub find_open_port_above {
20 | socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp"));
21 | bind(SOCK, sockaddr_in(0, INADDR_ANY));
22 | my $port = (sockaddr_in(getsockname(SOCK)))[0];
23 | close(SOCK);
24 | return $port;
25 | }
26 |
27 | sub find_open_port {
28 | my ($port) = @_;
29 |
30 | probe_port($port) ? return 0 : return $port;
31 | }
32 |
33 | sub probe_port {
34 | my ($port) = @_;
35 |
36 | return IO::Socket::INET->new(
37 | PeerAddr => '127.0.0.1',
38 | PeerPort => $port,
39 | Timeout => 3
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/lib/Selenium/Chrome.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Chrome;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Use ChromeDriver without a Selenium server
7 | use Moo;
8 | use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
9 | extends 'Selenium::Remote::Driver';
10 |
11 | =head1 SYNOPSIS
12 |
13 | my $driver = Selenium::Chrome->new;
14 | # when you're done
15 | $driver->shutdown_binary;
16 |
17 | =for Pod::Coverage has_binary
18 |
19 | =head1 DESCRIPTION
20 |
21 | This class allows you to use the ChromeDriver without needing the JRE
22 | or a selenium server running. When you refrain from passing the
23 | C and C arguments, we will search for the
24 | chromedriver executable binary in your $PATH. We'll try to start the
25 | binary connect to it, shutting it down at the end of the test.
26 |
27 | If the chromedriver binary is not found, we'll fall back to the
28 | default L behavior of assuming defaults of
29 | 127.0.0.1:4444 after waiting a few seconds.
30 |
31 | If you specify a remote server address, or a port, we'll assume you
32 | know what you're doing and take no additional behavior.
33 |
34 | If you're curious whether your Selenium::Chrome instance is using a
35 | separate ChromeDriver binary, or through the selenium server, you can
36 | check the C attr after instantiation.
37 |
38 | =cut
39 |
40 | has '+browser_name' => (
41 | is => 'ro',
42 | default => sub { 'chrome' }
43 | );
44 |
45 | =attr binary
46 |
47 | Optional: specify the path to your binary. If you don't specify
48 | anything, we'll try to find it on our own via L.
49 |
50 | =cut
51 |
52 | has 'binary' => (
53 | is => 'lazy',
54 | coerce => \&coerce_simple_binary,
55 | default => sub { 'chromedriver' },
56 | predicate => 1
57 | );
58 |
59 | =attr binary_port
60 |
61 | Optional: specify the port that we should bind to. If you don't
62 | specify anything, we'll default to the driver's default port. Since
63 | there's no a priori guarantee that this will be an open port, this is
64 | _not_ necessarily the port that we end up using - if the port here is
65 | already bound, we'll search above it until we find an open one.
66 |
67 | See L for more details, and
68 | L after instantiation to see what the
69 | actual port turned out to be.
70 |
71 | =cut
72 |
73 | has 'binary_port' => (
74 | is => 'lazy',
75 | default => sub { 9515 }
76 | );
77 |
78 | has '_binary_args' => (
79 | is => 'lazy',
80 | builder => sub {
81 | my ($self) = @_;
82 |
83 | my $context = $self->wd_context_prefix;
84 | $context =~ s{^/}{};
85 |
86 | return ' --port=' . $self->port . ' --url-base=' . $context . ' ';
87 | }
88 | );
89 |
90 | with 'Selenium::CanStartBinary';
91 |
92 | =attr custom_args
93 |
94 | Optional: specify any additional command line arguments you'd like
95 | invoked during the binary startup. See
96 | L for more information.
97 |
98 | =attr startup_timeout
99 |
100 | Optional: specify how long to wait for the binary to start itself and
101 | listen on its port. The default duration is arbitrarily 10 seconds. It
102 | accepts an integer number of seconds to wait: the following will wait
103 | up to 20 seconds:
104 |
105 | Selenium::Chrome->new( startup_timeout => 20 );
106 |
107 | See L for more information.
108 |
109 | =method shutdown_binary
110 |
111 | Call this method instead of L to ensure
112 | that the binary executable is also closed, instead of simply closing
113 | the browser itself. If the browser is still around, it will call
114 | C for you. After that, it will try to shutdown the browser
115 | binary by making a GET to /shutdown and on Windows, it will attempt to
116 | do a C on the binary CMD window.
117 |
118 | $self->shutdown_binary;
119 |
120 | It doesn't take any arguments, and it doesn't return anything.
121 |
122 | We do our best to call this when the C<$driver> option goes out of
123 | scope, but if that happens during global destruction, there's nothing
124 | we can do.
125 |
126 | =attr fixed_ports
127 |
128 | Optional: Throw instead of searching for additional ports; see
129 | L for more info.
130 |
131 | =cut
132 |
133 | 1;
134 |
--------------------------------------------------------------------------------
/lib/Selenium/Edge.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Edge;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Use EdgeDriver without a Selenium server
7 | use Moo;
8 | use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
9 | extends 'Selenium::Remote::Driver';
10 |
11 | =head1 SYNOPSIS
12 |
13 | my $driver = Selenium::Edge->new;
14 | # when you're done
15 | $driver->shutdown_binary;
16 |
17 | =for Pod::Coverage has_binary
18 |
19 | =head1 DESCRIPTION
20 |
21 | This class allows you to use the EdgeDriver without needing the JRE
22 | or a selenium server running. When you refrain from passing the
23 | C and C arguments, we will search for the
24 | edgedriver executable binary in your $PATH. We'll try to start the
25 | binary connect to it, shutting it down at the end of the test.
26 |
27 | If the MicrosoftWebDriver binary is not found, we'll fall back to the
28 | default L behavior of assuming defaults of
29 | 127.0.0.1:4444 after waiting a few seconds.
30 |
31 | If you specify a remote server address, or a port, we'll assume you
32 | know what you're doing and take no additional behavior.
33 |
34 | If you're curious whether your Selenium::Edge instance is using a
35 | separate MicrosoftWebDriver binary, or through the selenium server, you can
36 | check the C attr after instantiation.
37 |
38 | =cut
39 |
40 | has '+browser_name' => (
41 | is => 'ro',
42 | default => sub { 'MicrosoftEdge' }
43 | );
44 |
45 | =attr binary
46 |
47 | Optional: specify the path to your binary. If you don't specify
48 | anything, we'll try to find it on our own via L.
49 |
50 | =cut
51 |
52 | has 'binary' => (
53 | is => 'lazy',
54 | coerce => \&coerce_simple_binary,
55 | default => sub { 'msedgedriver.exe' },
56 | predicate => 1
57 | );
58 |
59 | =attr binary_port
60 |
61 | Optional: specify the port that we should bind to. If you don't
62 | specify anything, we'll default to the driver's default port. Since
63 | there's no a priori guarantee that this will be an open port, this is
64 | _not_ necessarily the port that we end up using - if the port here is
65 | already bound, we'll search above it until we find an open one.
66 |
67 | See L for more details, and
68 | L after instantiation to see what the
69 | actual port turned out to be.
70 |
71 | =cut
72 |
73 | has 'binary_port' => (
74 | is => 'lazy',
75 | default => sub { 17556 }
76 | );
77 |
78 | has '_binary_args' => (
79 | is => 'lazy',
80 | builder => sub {
81 | my ($self) = @_;
82 |
83 | my $context = $self->wd_context_prefix;
84 | $context =~ s{^/}{};
85 |
86 | return ' --port=' . $self->port . ' --url-base=' . $context . ' ';
87 | }
88 | );
89 |
90 | with 'Selenium::CanStartBinary';
91 |
92 | =attr custom_args
93 |
94 | Optional: specify any additional command line arguments you'd like
95 | invoked during the binary startup. See
96 | L for more information.
97 |
98 | =attr startup_timeout
99 |
100 | Optional: specify how long to wait for the binary to start itself and
101 | listen on its port. The default duration is arbitrarily 10 seconds. It
102 | accepts an integer number of seconds to wait: the following will wait
103 | up to 20 seconds:
104 |
105 | Selenium::Edge->new( startup_timeout => 20 );
106 |
107 | See L for more information.
108 |
109 | =method shutdown_binary
110 |
111 | Call this method instead of L to ensure
112 | that the binary executable is also closed, instead of simply closing
113 | the browser itself. If the browser is still around, it will call
114 | C for you. After that, it will try to shutdown the browser
115 | binary by making a GET to /shutdown and on Windows, it will attempt to
116 | do a C on the binary CMD window.
117 |
118 | $self->shutdown_binary;
119 |
120 | It doesn't take any arguments, and it doesn't return anything.
121 |
122 | We do our best to call this when the C<$driver> option goes out of
123 | scope, but if that happens during global destruction, there's nothing
124 | we can do.
125 |
126 | =attr fixed_ports
127 |
128 | Optional: Throw instead of searching for additional ports; see
129 | L for more info.
130 |
131 | =cut
132 |
133 | 1;
134 |
--------------------------------------------------------------------------------
/lib/Selenium/Firefox/Binary.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Firefox::Binary;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Subroutines for locating and properly initializing the Firefox Binary
7 | use File::Which qw/which/;
8 | use Selenium::Firefox::Profile;
9 |
10 | require Exporter;
11 | our @ISA = qw/Exporter/;
12 | our @EXPORT_OK = qw/firefox_path setup_firefox_binary_env/;
13 |
14 | sub _firefox_windows_path {
15 |
16 | # TODO: make this slightly less dumb
17 | my @program_files = (
18 | $ENV{PROGRAMFILES} // 'C:\Program Files',
19 | $ENV{'PROGRAMFILES(X86)'} // 'C:\Program Files (x86)',
20 | );
21 |
22 | foreach (@program_files) {
23 | my $binary_path = $_ . '\Mozilla Firefox\firefox.exe';
24 | return $binary_path if -x $binary_path;
25 | }
26 |
27 | # Fall back to a completely naive strategy
28 | warn
29 | q/We couldn't find a viable firefox.EXE; you may want to specify it via the binary attribute./;
30 | return which('firefox');
31 | }
32 |
33 | sub _firefox_darwin_path {
34 | my $default_firefox =
35 | '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
36 |
37 | if ( -e $default_firefox && -x $default_firefox ) {
38 | return $default_firefox;
39 | }
40 | else {
41 | return which('firefox-bin');
42 | }
43 | }
44 |
45 | sub _firefox_unix_path {
46 |
47 | # TODO: maybe which('firefox3'), which('firefox2') ?
48 | return which('firefox') || '/usr/bin/firefox';
49 | }
50 |
51 | =head1 SUBROUTINES
52 |
53 | =head2 firefox_path
54 |
55 | Return the path to the firefox binary on your system.
56 |
57 | =cut
58 |
59 | sub firefox_path {
60 | my $path;
61 | if ( $^O eq 'MSWin32' ) {
62 | $path = _firefox_windows_path();
63 | }
64 | elsif ( $^O eq 'darwin' ) {
65 | $path = _firefox_darwin_path();
66 | }
67 | else {
68 | $path = _firefox_unix_path;
69 | }
70 |
71 | if ( not -x $path ) {
72 | die $path . ' is not an executable file.';
73 | }
74 |
75 | return $path;
76 | }
77 |
78 | =head2 setup_firefox_binary_env
79 |
80 | Sets various environment variables to make firefox work correctly with webDriver.
81 |
82 | =cut
83 |
84 | # We want the profile to persist to the end of the session, not just
85 | # the end of this function.
86 | my $profile;
87 |
88 | sub setup_firefox_binary_env {
89 | my ( $port, $marionette_port, $caller_profile ) = @_;
90 |
91 | $profile = $caller_profile || Selenium::Firefox::Profile->new;
92 | $profile->add_webdriver( $port, $marionette_port );
93 | $profile->add_marionette($marionette_port);
94 |
95 | # For non-geckodriver/marionette startup, we instruct Firefox to
96 | # use the profile by specifying the appropriate environment
97 | # variables for it to hook onto.
98 | if ( !$marionette_port ) {
99 | $ENV{'XRE_PROFILE_PATH'} = $profile->_layout_on_disk;
100 | $ENV{'MOZ_NO_REMOTE'} = '1'; # able to launch multiple instances
101 | $ENV{'MOZ_CRASHREPORTER_DISABLE'} = '1'; # disable breakpad
102 | $ENV{'NO_EM_RESTART'} =
103 | '1'; # prevent the binary from detaching from the console.log
104 | }
105 | else {
106 | # In case the user created an old Firefox, which would've set
107 | # those ENV variables, and then wanted to create a new Firefox
108 | # afterwards, the env variables would still be around, and the
109 | # new Firefox would respect the XRE_PROFILE_PATH and try to
110 | # load it in the new geckodriver Firefox, which would cause an
111 | # extension compatibility check
112 | my @env_vars = qw/
113 | XRE_PROFILE_PATH
114 | MOZ_NO_REMOTE
115 | MOZ_CRASHREPORTER_DISABLE
116 | NO_EM_RESTART
117 | /;
118 |
119 | foreach (@env_vars) {
120 | delete $ENV{$_};
121 | }
122 | }
123 |
124 | return $profile;
125 | }
126 |
127 | 1;
128 |
--------------------------------------------------------------------------------
/lib/Selenium/Firefox/Profile.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Firefox::Profile;
2 |
3 | # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
4 | # TODO: convert this to Moo!
5 |
6 | use strict;
7 | use warnings;
8 |
9 | use Archive::Zip qw( :ERROR_CODES );
10 | use Carp qw(croak);
11 | use Cwd qw(abs_path);
12 | use File::Copy qw(copy);
13 | use File::Temp;
14 | use File::Basename qw(dirname);
15 | use IO::Uncompress::Unzip 2.030 qw($UnzipError);
16 | use JSON qw(decode_json);
17 | use MIME::Base64;
18 | use Scalar::Util qw(blessed looks_like_number);
19 | use XML::Simple;
20 |
21 | =head1 DESCRIPTION
22 |
23 | You can use this module to create a custom Firefox Profile for your
24 | Selenium tests. Currently, you can set browser preferences and add
25 | extensions to the profile before passing it in the constructor for a
26 | new L or L.
27 |
28 | =head1 SYNPOSIS
29 |
30 | use Selenium::Remote::Driver;
31 | use Selenium::Firefox::Profile;
32 |
33 | my $profile = Selenium::Firefox::Profile->new;
34 | $profile->set_preference(
35 | 'browser.startup.homepage' => 'http://www.google.com',
36 | 'browser.cache.disk.capacity' => 358400
37 | );
38 |
39 | $profile->set_boolean_preference(
40 | 'browser.shell.checkDefaultBrowser' => 0
41 | );
42 |
43 | $profile->add_extension('t/www/redisplay.xpi');
44 |
45 | my $driver = Selenium::Remote::Driver->new(
46 | 'firefox_profile' => $profile
47 | );
48 |
49 | $driver->get('http://www.google.com');
50 | print $driver->get_title();
51 |
52 | =cut
53 |
54 | =head1 CONSTRUCTOR
55 |
56 | =head2 new (%args)
57 |
58 | profile_dir - directory to look for the firefox profile. Defaults to a Tempdir.
59 |
60 | =cut
61 |
62 | sub new {
63 | my $class = shift;
64 | my %args = @_;
65 |
66 | my $profile_dir;
67 | if ( $args{profile_dir} && -d $args{profile_dir} ) {
68 | $profile_dir = $args{profile_dir};
69 | }
70 | else {
71 | $profile_dir = File::Temp->newdir();
72 | }
73 |
74 | # TODO: accept user prefs, boolean prefs, and extensions in
75 | # constructor
76 | my $self = {
77 | profile_dir => $profile_dir,
78 | user_prefs => {},
79 | extensions => []
80 | };
81 | bless $self, $class or die "Can't bless $class: $!";
82 |
83 | return $self;
84 | }
85 |
86 | =head1 METHODS
87 |
88 | =head2 set_preference
89 |
90 | Set string and integer preferences on the profile object. You can set
91 | multiple preferences at once. If you need to set a boolean preference,
92 | either use JSON::true/JSON::false, or see C.
93 |
94 | $profile->set_preference("quoted.integer.pref" => '"20140314220517"');
95 | # user_pref("quoted.integer.pref", "20140314220517");
96 |
97 | $profile->set_preference("plain.integer.pref" => 9005);
98 | # user_pref("plain.integer.pref", 9005);
99 |
100 | $profile->set_preference("string.pref" => "sample string value");
101 | # user_pref("string.pref", "sample string value");
102 |
103 | =cut
104 |
105 | sub set_preference {
106 | my ( $self, %prefs ) = @_;
107 |
108 | foreach ( keys %prefs ) {
109 | my $value = $prefs{$_};
110 | my $clean_value = '';
111 |
112 | if ( JSON::is_bool($value) ) {
113 | $self->set_boolean_preference( $_, $value );
114 | next;
115 | }
116 | elsif ( $value =~ /^(['"]).*\1$/ or looks_like_number($value) ) {
117 |
118 | # plain integers: 0, 1, 32768, or integers wrapped in strings:
119 | # "0", "1", "20140204". in either case, there's nothing for us
120 | # to do.
121 | $clean_value = $value;
122 | }
123 | else {
124 | # otherwise it's hopefully a string that we'll need to
125 | # quote on our own
126 | $clean_value = '"' . $value . '"';
127 | }
128 |
129 | $self->{user_prefs}->{$_} = $clean_value;
130 | }
131 | }
132 |
133 | =head2 set_boolean_preference
134 |
135 | Set preferences that require boolean values of 'true' or 'false'. You
136 | can set multiple preferences at once. For string or integer
137 | preferences, use C.
138 |
139 | $profile->set_boolean_preference("false.pref" => 0);
140 | # user_pref("false.pref", false);
141 |
142 | $profile->set_boolean_preference("true.pref" => 1);
143 | # user_pref("true.pref", true);
144 |
145 | =cut
146 |
147 | sub set_boolean_preference {
148 | my ( $self, %prefs ) = @_;
149 |
150 | foreach ( keys %prefs ) {
151 | my $value = $prefs{$_};
152 |
153 | $self->{user_prefs}->{$_} = $value ? 'true' : 'false';
154 | }
155 | }
156 |
157 | =head2 get_preference
158 |
159 | Retrieve the computed value of a preference. Strings will be double
160 | quoted and boolean values will be single quoted as "true" or "false"
161 | accordingly.
162 |
163 | $profile->set_boolean_preference("true.pref" => 1);
164 | print $profile->get_preference("true.pref") # true
165 |
166 | $profile->set_preference("string.pref" => "an extra set of quotes");
167 | print $profile->get_preference("string.pref") # "an extra set of quotes"
168 |
169 | =cut
170 |
171 | sub get_preference {
172 | my ( $self, $pref ) = @_;
173 |
174 | return $self->{user_prefs}->{$pref};
175 | }
176 |
177 | =head2 add_extension
178 |
179 | Add an existing C<.xpi> to the profile by providing its path. This
180 | only works with packaged C<.xpi> files, not plain/un-packed extension
181 | directories.
182 |
183 | $profile->add_extension('t/www/redisplay.xpi');
184 |
185 | =cut
186 |
187 | sub add_extension {
188 | my ( $self, $xpi ) = @_;
189 |
190 | croak 'File not found: ' . $xpi unless -e $xpi;
191 | my $xpi_abs_path = abs_path($xpi);
192 | croak '$xpi_abs_path: extensions must be in .xpi format'
193 | unless $xpi_abs_path =~ /\.xpi$/;
194 |
195 | push( @{ $self->{extensions} }, $xpi_abs_path );
196 | }
197 |
198 | =head2 add_webdriver
199 |
200 | Primarily for internal use, we set the appropriate firefox preferences
201 | for a new geckodriver session.
202 |
203 | =cut
204 |
205 | sub add_webdriver {
206 | my ( $self, $port, $is_marionette ) = @_;
207 |
208 | my $current_user_prefs = $self->{user_prefs};
209 |
210 | $self->set_preference(
211 | # having the user prefs here allows them to overwrite the
212 | # mutable loaded prefs
213 | %{$current_user_prefs},
214 |
215 | # but the frozen ones cannot be overwritten
216 | 'webdriver_firefox_port' => $port
217 | );
218 |
219 | if ( !$is_marionette ) {
220 | $self->_add_webdriver_xpi;
221 | }
222 |
223 | return $self;
224 | }
225 |
226 |
227 | =head2 add_webdriver_xpi
228 |
229 | Primarily for internal use. This adds the fxgoogle .xpi that is used
230 | for webdriver communication in FF47 and older. For FF48 and newer, the
231 | old method using an extension to orchestrate the webdriver
232 | communication with the Firefox browser has been obsoleted by the
233 | introduction of C.
234 |
235 | =cut
236 |
237 | sub _add_webdriver_xpi {
238 | my ($self) = @_;
239 |
240 | my $this_dir = dirname( abs_path(__FILE__) );
241 | my $webdriver_extension = $this_dir . '/webdriver.xpi';
242 |
243 | $self->add_extension($webdriver_extension);
244 | }
245 |
246 | =head2 add_marionette
247 |
248 | Primarily for internal use, configure Marionette to the
249 | current Firefox profile.
250 |
251 | =cut
252 |
253 | sub add_marionette {
254 | my ( $self, $port ) = @_;
255 | return if !$port;
256 | $self->set_preference( 'marionette.defaultPrefs.port', $port );
257 | }
258 |
259 | sub _encode {
260 | my $self = shift;
261 |
262 | # The remote webdriver accepts the Firefox profile as a base64
263 | # encoded zip file
264 | $self->_layout_on_disk();
265 |
266 | my $zip = Archive::Zip->new();
267 | $zip->addTree( $self->{profile_dir} );
268 |
269 | my $string = "";
270 | open( my $fh, ">", \$string );
271 | binmode($fh);
272 | unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
273 | die 'write error';
274 | }
275 |
276 | return encode_base64( $string, '' );
277 | }
278 |
279 | sub _layout_on_disk {
280 | my $self = shift;
281 |
282 | $self->_write_preferences();
283 | $self->_install_extensions();
284 |
285 | return $self->{profile_dir};
286 | }
287 |
288 | sub _write_preferences {
289 | my $self = shift;
290 |
291 | my $userjs = $self->{profile_dir} . "/user.js";
292 | open( my $fh, ">>", $userjs )
293 | or die "Cannot open $userjs for writing preferences: $!";
294 |
295 | foreach ( keys %{ $self->{user_prefs} } ) {
296 | print $fh 'user_pref("'
297 | . $_ . '", '
298 | . $self->get_preference($_) . ');' . "\n";
299 | }
300 | close($fh);
301 | }
302 |
303 | sub _install_extensions {
304 | my $self = shift;
305 | my $extension_dir = $self->{profile_dir} . "/extensions/";
306 | mkdir $extension_dir unless -d $extension_dir;
307 |
308 | # TODO: handle extensions that need to be unpacked
309 | foreach my $xpi ( @{ $self->{extensions} } ) {
310 |
311 | # For Firefox to recognize the extension, we have to put the
312 | # .xpi in the /extensions/ folder and change the filename to
313 | # its id, which is found in the install.rdf in the root of the
314 | # zip.
315 |
316 | my $rdf_string = $self->_extract_install_rdf($xpi);
317 | my $rdf = XMLin($rdf_string);
318 | my $name = $rdf->{Description}->{'em:id'};
319 |
320 | my $xpi_dest = $extension_dir . $name . ".xpi";
321 | copy( $xpi, $xpi_dest )
322 | or croak "Error copying $_ to $xpi_dest : $!";
323 | }
324 | }
325 |
326 | sub _extract_install_rdf {
327 | my ( $self, $xpi ) = @_;
328 |
329 | my $unzipped = IO::Uncompress::Unzip->new($xpi)
330 | or die "Cannot unzip $xpi: $UnzipError";
331 |
332 | my $install_rdf = '';
333 | while ( $unzipped->nextStream ) {
334 | my $filename = $unzipped->getHeaderInfo->{Name};
335 | if ( $filename eq 'install.rdf' ) {
336 | my $buffer;
337 | while ( ( my $status = $unzipped->read($buffer) ) > 0 ) {
338 | $install_rdf .= $buffer;
339 | }
340 | return $install_rdf;
341 | }
342 | }
343 |
344 | croak
345 | 'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
346 | . $xpi;
347 | }
348 |
349 | 1;
350 |
351 | __END__
352 |
353 | =head1 SEE ALSO
354 |
355 | http://kb.mozillazine.org/About:config_entries
356 | https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences
357 |
--------------------------------------------------------------------------------
/lib/Selenium/Firefox/webdriver.xpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teodesian/Selenium-Remote-Driver/1f078d8f41ae02cbeb51d0a6ad460c0aee6c98fc/lib/Selenium/Firefox/webdriver.xpi
--------------------------------------------------------------------------------
/lib/Selenium/InternetExplorer.pm:
--------------------------------------------------------------------------------
1 | package Selenium::InternetExplorer;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: A convenience package for creating a IE instance
7 | use Moo;
8 | extends 'Selenium::Remote::Driver';
9 |
10 | =head1 SYNOPSIS
11 |
12 | my $driver = Selenium::InternetExplorer->new;
13 | # when you're done
14 | $driver->shutdown_binary;
15 |
16 | =cut
17 |
18 | has '+browser_name' => (
19 | is => 'ro',
20 | default => sub { 'internet_explorer' }
21 | );
22 |
23 | has '+platform' => (
24 | is => 'ro',
25 | default => sub { 'WINDOWS' }
26 | );
27 |
28 | =method shutdown_binary
29 |
30 | Call this method instead of L to ensure
31 | that the binary executable is also closed, instead of simply closing
32 | the browser itself. If the browser is still around, it will call
33 | C for you. After that, it will try to shutdown the browser
34 | binary by making a GET to /shutdown and on Windows, it will attempt to
35 | do a C on the binary CMD window.
36 |
37 | $self->shutdown_binary;
38 |
39 | It doesn't take any arguments, and it doesn't return anything.
40 |
41 | We do our best to call this when the C<$driver> option goes out of
42 | scope, but if that happens during global destruction, there's nothing
43 | we can do.
44 |
45 | =cut
46 |
47 | 1;
48 |
--------------------------------------------------------------------------------
/lib/Selenium/PhantomJS.pm:
--------------------------------------------------------------------------------
1 | package Selenium::PhantomJS;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Use GhostDriver without a Selenium server
7 | use Moo;
8 | use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
9 | extends 'Selenium::Remote::Driver';
10 |
11 | =for Pod::Coverage has_binary
12 |
13 | =head1 SYNOPSIS
14 |
15 | my $driver = Selenium::PhantomJS->new;
16 | # when you're done
17 | $driver->shutdown_binary;
18 |
19 | =head1 DESCRIPTION
20 |
21 | This class allows you to use PhantomJS via Ghostdriver without needing
22 | the JRE or a selenium server running. When you refrain from passing
23 | the C and C arguments, we will search for
24 | the phantomjs executable binary in your $PATH. We'll try to start the
25 | binary connect to it, shutting it down at the end of the test.
26 |
27 | If the binary is not found, we'll fall back to the default
28 | L behavior of assuming defaults of
29 | 127.0.0.1:4444 after waiting a few seconds.
30 |
31 | If you specify a remote server address, or a port, we'll assume you
32 | know what you're doing and take no additional behavior.
33 |
34 | If you're curious whether your Selenium::PhantomJS instance is using a
35 | separate PhantomJS binary, or through the selenium server, you can check
36 | the C attr after instantiation.
37 |
38 | my $driver = Selenium::PhantomJS->new;
39 | print $driver->binary_mode;
40 |
41 | N.B. - if you're using Windows and you installed C via
42 | C, there is a very high probability that we
43 | will _not_ close down your phantomjs binary correctly after your
44 | test. You will be able to tell if we leave around empty command
45 | windows that you didn't start yourself. The easiest way to fix this is
46 | to download PhantomJS manually from their
47 | L and put it in your
48 | C<%PATH%>. If this is a blocking issue for you, let us know in
49 | L; thanks!
50 |
51 | =cut
52 |
53 | has '+browser_name' => (
54 | is => 'ro',
55 | default => sub { 'phantomjs' }
56 | );
57 |
58 | =attr binary
59 |
60 | Optional: specify the path to your binary. If you don't specify
61 | anything, we'll try to find it on our own via L.
62 |
63 | =cut
64 |
65 | has 'binary' => (
66 | is => 'lazy',
67 | coerce => \&coerce_simple_binary,
68 | default => sub { 'phantomjs' },
69 | predicate => 1
70 | );
71 |
72 | =attr binary_port
73 |
74 | Optional: specify the port that we should bind to. If you don't
75 | specify anything, we'll default to the driver's default port. Since
76 | there's no a priori guarantee that this will be an open port, this is
77 | _not_ necessarily the port that we end up using - if the port here is
78 | already bound, we'll search above it until we find an open one.
79 |
80 | See L for more details, and
81 | L after instantiation to see what the
82 | actual port turned out to be.
83 |
84 | =cut
85 |
86 | has 'binary_port' => (
87 | is => 'lazy',
88 | default => sub { 8910 }
89 | );
90 |
91 | has '_binary_args' => (
92 | is => 'lazy',
93 | builder => sub {
94 | my ($self) = @_;
95 |
96 | return ' --webdriver=127.0.0.1:' . $self->port;
97 | }
98 | );
99 |
100 | with 'Selenium::CanStartBinary';
101 |
102 | =attr custom_args
103 |
104 | Optional: specify any additional command line arguments you'd like
105 | invoked during the binary startup. See
106 | L for more information.
107 |
108 | =attr startup_timeout
109 |
110 | Optional: specify how long to wait for the binary to start itself and
111 | listen on its port. The default duration is arbitrarily 10 seconds. It
112 | accepts an integer number of seconds to wait: the following will wait
113 | up to 20 seconds:
114 |
115 | Selenium::PhantomJS->new( startup_timeout => 20 );
116 |
117 | See L for more information.
118 |
119 | =method shutdown_binary
120 |
121 | Call this method instead of L to ensure
122 | that the binary executable is also closed, instead of simply closing
123 | the browser itself. If the browser is still around, it will call
124 | C for you. After that, it will try to shutdown the browser
125 | binary by making a GET to /shutdown and on Windows, it will attempt to
126 | do a C on the binary CMD window.
127 |
128 | $self->shutdown_binary;
129 |
130 | It doesn't take any arguments, and it doesn't return anything.
131 |
132 | We do our best to call this when the C<$driver> option goes out of
133 | scope, but if that happens during global destruction, there's nothing
134 | we can do.
135 |
136 | =cut
137 |
138 | 1;
139 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/Driver/CanSetWebdriverContext.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::Driver::CanSetWebdriverContext;
2 |
3 | # ABSTRACT: Customize the webdriver context prefix for various drivers
4 |
5 | use strict;
6 | use warnings;
7 |
8 | use Moo::Role;
9 |
10 | =head1 DESCRIPTION
11 |
12 | Some drivers don't use the typical C context prefix for the
13 | webdriver HTTP communication. For example, the newer versions of the
14 | Firefox driver extension use the context C instead. This role
15 | just has the one attribute with a default webdriver context prefix,
16 | and is consumed in L and
17 | L.
18 |
19 | If you're new to webdriver, you probably want to head over to
20 | L's docs; this package is more of an
21 | internal-facing concern.
22 |
23 | =cut
24 |
25 | has 'wd_context_prefix' => (
26 | is => 'lazy',
27 | default => sub { '/wd/hub' }
28 | );
29 |
30 | 1;
31 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/Driver/Firefox/Profile.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::Driver::Firefox::Profile;
2 |
3 | # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
4 | use strict;
5 | use warnings;
6 |
7 | use Selenium::Firefox::Profile;
8 |
9 | BEGIN {
10 | push our @ISA, 'Selenium::Firefox::Profile';
11 | }
12 |
13 | =head1 DESCRIPTION
14 |
15 | We've renamed this class to the slightly less wordy
16 | L. This is only around as an alias to
17 | hopefully prevent old code from breaking.
18 |
19 | =cut
20 |
21 | 1;
22 |
23 | =head1 SEE ALSO
24 |
25 | Selenium::Firefox::Profile
26 | http://kb.mozillazine.org/About:config_entries
27 | https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences
28 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/ErrorHandler.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::ErrorHandler;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Error handler for Selenium::Remote::Driver
7 |
8 | use Moo;
9 | use Carp qw(croak);
10 |
11 | # We're going to handle only codes that are errors.
12 | # http://code.google.com/p/selenium/wiki/JsonWireProtocol
13 | has STATUS_CODE => (
14 | is => 'lazy',
15 | builder => sub {
16 | return {
17 | 7 => {
18 | 'code' => 'NO_SUCH_ELEMENT',
19 | 'msg' =>
20 | 'An element could not be located on the page using the given search parameters.',
21 | },
22 | 8 => {
23 | 'code' => 'NO_SUCH_FRAME',
24 | 'msg' =>
25 | 'A request to switch to a frame could not be satisfied because the frame could not be found.',
26 | },
27 | 9 => {
28 | 'code' => 'UNKNOWN_COMMAND',
29 | 'msg' =>
30 | 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.',
31 | },
32 | 10 => {
33 | 'code' => 'STALE_ELEMENT_REFERENCE',
34 | 'msg' =>
35 | 'An element command failed because the referenced element is no longer attached to the DOM.',
36 | },
37 | 11 => {
38 | 'code' => 'ELEMENT_NOT_VISIBLE',
39 | 'msg' =>
40 | 'An element command could not be completed because the element is not visible on the page.',
41 | },
42 | 12 => {
43 | 'code' => 'INVALID_ELEMENT_STATE',
44 | 'msg' =>
45 | 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).',
46 | },
47 | 13 => {
48 | 'code' => 'UNKNOWN_ERROR',
49 | 'msg' =>
50 | 'An unknown server-side error occurred while processing the command.',
51 | },
52 | 15 => {
53 | 'code' => 'ELEMENT_IS_NOT_SELECTABLE',
54 | 'msg' =>
55 | 'An attempt was made to select an element that cannot be selected.',
56 | },
57 | 19 => {
58 | 'code' => 'XPATH_LOOKUP_ERROR',
59 | 'msg' =>
60 | 'An error occurred while searching for an element by XPath.',
61 | },
62 | 21 => {
63 | 'code' => 'Timeout',
64 | 'msg' =>
65 | 'An operation did not complete before its timeout expired.',
66 | },
67 | 23 => {
68 | 'code' => 'NO_SUCH_WINDOW',
69 | 'msg' =>
70 | 'A request to switch to a different window could not be satisfied because the window could not be found.',
71 | },
72 | 24 => {
73 | 'code' => 'INVALID_COOKIE_DOMAIN',
74 | 'msg' =>
75 | 'An illegal attempt was made to set a cookie under a different domain than the current page.',
76 | },
77 | 25 => {
78 | 'code' => 'UNABLE_TO_SET_COOKIE',
79 | 'msg' =>
80 | 'A request to set a cookie\'s value could not be satisfied.',
81 | },
82 | 26 => {
83 | 'code' => 'UNEXPECTED_ALERT_OPEN',
84 | 'msg' => 'A modal dialog was open, blocking this operation',
85 | },
86 | 27 => {
87 | 'code' => 'NO_ALERT_OPEN_ERROR',
88 | 'msg' =>
89 | 'An attempt was made to operate on a modal dialog when one was not open.',
90 | },
91 | 28 => {
92 | 'code' => 'SCRIPT_TIMEOUT',
93 | 'msg' =>
94 | 'A script did not complete before its timeout expired.',
95 | },
96 | 29 => {
97 | 'code' => 'INVALID_ELEMENT_COORDINATES',
98 | 'msg' =>
99 | 'The coordinates provided to an interactions operation are invalid.',
100 | },
101 | 30 => {
102 | 'code' => 'IME_NOT_AVAILABLE',
103 | 'msg' => 'IME was not available.',
104 | },
105 | 31 => {
106 | 'code' => 'IME_ENGINE_ACTIVATION_FAILED',
107 | 'msg' => 'An IME engine could not be started.',
108 | },
109 | 32 => {
110 | 'code' => 'INVALID_SELECTOR',
111 | 'msg' => 'Argument was an invalid selector (e.g. XPath/CSS).',
112 | },
113 | };
114 | }
115 | );
116 |
117 | =head1 SUBROUTINES
118 |
119 | =head2 process_error (Selenium::Remote::Driver $driver, HTTP::Response $response)
120 |
121 | Instead of just returning the end user a server returned error code, this returns a more human readable & usable error message.
122 |
123 | Used internally in Selenium::Remote::Driver, but overriding this might be useful in some situations.
124 | You could additionally alter the STATUS_CODE parameter of this module to add extra handlers if the situation warrants it.
125 |
126 | =cut
127 |
128 | sub process_error {
129 | my ( $self, $resp ) = @_;
130 |
131 | # TODO: Handle screen if it sent back with the response. Either we could
132 | # let the end user handle it or we can save it an image file at a temp
133 | # location & return the path.
134 |
135 | # handle stacktrace-only responses by assuming unknown error
136 | my $is_stacktrace = !$resp->{status};
137 | $resp->{status} = 13 unless $resp->{status};
138 |
139 | my $ret;
140 |
141 | #XXX capitalization is inconsistent among geckodriver versions
142 | $ret->{'stackTrace'} = $resp->{'value'}->{'stacktrace'}
143 | // $resp->{'value'}->{'stackTrace'};
144 | $ret->{'error'} =
145 | $is_stacktrace
146 | ? $resp->{value}->{error}
147 | : $self->STATUS_CODE->{ $resp->{'status'} };
148 | $ret->{'message'} = $resp->{'value'}->{'message'};
149 |
150 | return $ret;
151 | }
152 |
153 | 1;
154 |
155 | __END__
156 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/Finders.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::Finders;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Handle construction of generic parameter finders
7 | use Try::Tiny;
8 | use Carp qw/carp/;
9 | use Moo::Role;
10 | use namespace::clean;
11 |
12 | =head1 DESCRIPTION
13 |
14 | This package just takes care of setting up parameter finders - that
15 | is, the C versions of the find element
16 | functions. You probably don't need to do anything with this package;
17 | instead, see L documentation
18 | for the specific finder functions.
19 |
20 | =cut
21 |
22 | sub _build_find_by {
23 | my ( $self, $by ) = @_;
24 |
25 | return sub {
26 | my ( $driver, $locator ) = @_;
27 | my $strategy = $by;
28 |
29 | return try {
30 | return $driver->find_element( $locator, $strategy );
31 | }
32 | catch {
33 | carp $_;
34 | return 0;
35 | };
36 | }
37 | }
38 |
39 | 1;
40 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/Mock/Commands.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::Mock::Commands;
2 |
3 | # ABSTRACT: utility class to mock Selenium::Remote::Commands
4 |
5 | use strict;
6 | use warnings;
7 |
8 | use Moo;
9 | extends 'Selenium::Remote::Commands';
10 |
11 | =for Pod::Coverage *EVERYTHING*
12 |
13 | =cut
14 |
15 | # override get_params so we do not rewrite the parameters
16 |
17 | sub get_params {
18 | my $self = shift;
19 | my $args = shift;
20 | my $data = {};
21 | my $command = delete $args->{command};
22 | $data->{'url'} = $self->get_url($command);
23 | $data->{'method'} = $self->get_method($command);
24 | $data->{'no_content_success'} = $self->get_no_content_success($command);
25 | $data->{'url_params'} = $args;
26 | return $data;
27 | }
28 |
29 | sub get_method_name_from_parameters {
30 | my $self = shift;
31 | my $params = shift;
32 | my $method_name = '';
33 | my $cmds = $self->get_cmds();
34 | foreach my $cmd ( keys %{$cmds} ) {
35 | if ( ( $cmds->{$cmd}->{method} eq $params->{method} )
36 | && ( $cmds->{$cmd}->{url} eq $params->{url} ) )
37 | {
38 | $method_name = $cmd;
39 | last;
40 | }
41 | }
42 | return $method_name;
43 | }
44 |
45 | 1;
46 |
47 | __END__
48 |
49 | =pod
50 |
51 | =head1 DESCRIPTION
52 |
53 | Utility class to be for testing purposes, with L only.
54 |
55 | =cut
56 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/RemoteConnection.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::RemoteConnection;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | #ABSTRACT: Connect to a selenium server
7 |
8 | use Moo;
9 | use Try::Tiny;
10 | use LWP::UserAgent;
11 | use HTTP::Headers;
12 | use HTTP::Request;
13 | use Carp qw(croak);
14 | use JSON;
15 | use Data::Dumper;
16 | use Selenium::Remote::ErrorHandler;
17 | use Scalar::Util qw{looks_like_number};
18 |
19 | has 'remote_server_addr' => ( is => 'rw', );
20 |
21 | has 'port' => ( is => 'rw', );
22 |
23 | has 'debug' => (
24 | is => 'rw',
25 | default => sub { 0 }
26 | );
27 |
28 | has 'ua' => (
29 | is => 'lazy',
30 | builder => sub { return LWP::UserAgent->new; }
31 | );
32 |
33 | has 'error_handler' => (
34 | is => 'lazy',
35 | builder => sub { return Selenium::Remote::ErrorHandler->new; }
36 | );
37 |
38 | with 'Selenium::Remote::Driver::CanSetWebdriverContext';
39 |
40 | =head1 DESCRIPTION
41 |
42 | You shouldn't really need to use this module unless debugging or checking connections when testing dangerous things.
43 |
44 | =head1 SYNOPSIS
45 |
46 | my $driver = Selenium::Remote::Driver->new();
47 | eval { $driver->remote_conn->check_status() };
48 | die "do something to kick the server" if $@;
49 |
50 | =head1 CONSTRUCTOR
51 |
52 | =head2 new(%parameters)
53 |
54 | Accepts 5 parameters:
55 |
56 | =over 4
57 |
58 | =item B - address of selenium server
59 |
60 | =item B - port of selenium server
61 |
62 | =item B - Useful to override with Test::LWP::UserAgent in unit tests
63 |
64 | =item B - Should be self-explanatory
65 |
66 | =item B - Defaults to Selenium::Remote::ErrorHandler.
67 |
68 | =back
69 |
70 | These can be set any time later by getter/setters with the same name.
71 |
72 | =head1 METHODS
73 |
74 | =head2 check_status
75 |
76 | Croaks unless the selenium server is responsive. Sometimes is useful to call in-between tests (the server CAN die on you...)
77 |
78 | =cut
79 |
80 | sub check_status {
81 | my $self = shift;
82 | my $status;
83 |
84 | try {
85 | $status = $self->request( { method => 'GET', url => 'status' } );
86 | }
87 | catch {
88 | croak "Could not connect to SeleniumWebDriver: $_";
89 | };
90 |
91 | my $cmdOut = $status->{cmd_status} || '';
92 | if ( $cmdOut ne 'OK' ) {
93 |
94 | # Could be grid, see if we can talk to it
95 | $status = undef;
96 | $status =
97 | $self->request( { method => 'GET', url => 'grid/api/hub/status' } );
98 | }
99 |
100 | unless ( $cmdOut eq 'OK' ) {
101 | croak "Selenium server did not return proper status";
102 | }
103 | }
104 |
105 | =head2 request
106 |
107 | Make a request of the Selenium server. Mostly useful for debugging things going wrong with Selenium::Remote::Driver when not in normal operation.
108 |
109 | =cut
110 |
111 | sub request {
112 | my ( $self, $resource, $params, $dont_process_response ) = @_;
113 | my $method = $resource->{method};
114 | my $url = $resource->{url};
115 | my $no_content_success = $resource->{no_content_success} // 0;
116 |
117 | my $content = '';
118 | my $fullurl = '';
119 |
120 | # Construct full url.
121 | if ( $url =~ m/^http/g ) {
122 | $fullurl = $url;
123 | }
124 | elsif ( $url =~ m/^\// ) {
125 |
126 | # This is used when we get a 302 Redirect with a Location header.
127 | $fullurl =
128 | "http://" . $self->remote_server_addr . ":" . $self->port . $url;
129 | }
130 | elsif ( $url =~ m/grid/g ) {
131 | $fullurl =
132 | "http://" . $self->remote_server_addr . ":" . $self->port . "/$url";
133 | }
134 | else {
135 | $fullurl =
136 | "http://"
137 | . $self->remote_server_addr . ":"
138 | . $self->port
139 | . $self->wd_context_prefix . "/$url";
140 | }
141 |
142 | if ( ( defined $params ) && $params ne '' ) {
143 |
144 | #WebDriver 3 shims
145 | if ( $resource->{payload} ) {
146 | foreach my $key ( keys( %{ $resource->{payload} } ) ) {
147 | $params->{$key} = $resource->{payload}->{$key};
148 | }
149 | }
150 |
151 | my $json = JSON->new;
152 | $json->allow_blessed;
153 | $content = $json->allow_nonref->utf8->encode($params);
154 | }
155 |
156 | print "REQ: $method, $fullurl, $content\n" if $self->debug;
157 |
158 | # HTTP request
159 | my $header =
160 | HTTP::Headers->new( Content_Type => 'application/json; charset=utf-8' );
161 | $header->header( 'Accept' => 'application/json' );
162 | my $request = HTTP::Request->new( $method, $fullurl, $header, $content );
163 | my $response = $self->ua->request($request);
164 | if ($dont_process_response) {
165 | return $response;
166 | }
167 | return $self->_process_response( $response, $no_content_success );
168 | }
169 |
170 | sub _process_response {
171 | my ( $self, $response, $no_content_success ) = @_;
172 | my $data; # server response 'value' that'll be returned to the user
173 | my $json = JSON->new;
174 |
175 | if ( $response->is_redirect ) {
176 | my $redirect = {
177 | method => 'GET',
178 | url => $response->header('location')
179 | };
180 | return $self->request($redirect);
181 | }
182 | else {
183 | my $decoded_json = undef;
184 | print "RES: " . $response->decoded_content . "\n\n" if $self->debug;
185 |
186 | if ( ( $response->message ne 'No Content' )
187 | && ( $response->content ne '' ) )
188 | {
189 | if ( $response->content_type !~ m/json/i ) {
190 | $data->{'cmd_status'} = 'NOTOK';
191 | $data->{'cmd_return'}->{message} =
192 | 'Server returned error message '
193 | . $response->content
194 | . ' instead of data';
195 | return $data;
196 | }
197 | $decoded_json =
198 | $json->allow_nonref(1)->utf8(1)->decode( $response->content );
199 | $data->{'sessionId'} = $decoded_json->{'sessionId'};
200 | }
201 |
202 | if ( $response->is_error ) {
203 | $data->{'cmd_status'} = 'NOTOK';
204 | if ( defined $decoded_json ) {
205 | $data->{'cmd_return'} =
206 | $self->error_handler->process_error($decoded_json);
207 | }
208 | else {
209 | $data->{'cmd_return'} =
210 | 'Server returned error code '
211 | . $response->code
212 | . ' and no data';
213 | }
214 | return $data;
215 | }
216 | elsif ( $response->is_success ) {
217 | $data->{'cmd_status'} = 'OK';
218 | if ( defined $decoded_json ) {
219 |
220 | #XXX MS edge doesn't follow spec here either
221 | if ( looks_like_number( $decoded_json->{status} )
222 | && $decoded_json->{status} > 0
223 | && $decoded_json->{value}{message} )
224 | {
225 | $data->{cmd_status} = 'NOT OK';
226 | $data->{cmd_return} = $decoded_json->{value};
227 | return $data;
228 | }
229 |
230 | #XXX shockingly, neither does InternetExplorerDriver
231 | if ( ref $decoded_json eq 'HASH' && $decoded_json->{error} ) {
232 | $data->{cmd_status} = 'NOT OK';
233 | $data->{cmd_return} = $decoded_json;
234 | return $data;
235 | }
236 |
237 | if ($no_content_success) {
238 | $data->{'cmd_return'} = 1;
239 | }
240 | else {
241 | $data->{'cmd_return'} = $decoded_json->{'value'};
242 | if ( ref( $data->{cmd_return} ) eq 'HASH'
243 | && exists $data->{cmd_return}->{sessionId} )
244 | {
245 | $data->{sessionId} = $data->{cmd_return}->{sessionId};
246 | }
247 | }
248 | }
249 | else {
250 | $data->{'cmd_return'} =
251 | 'Server returned status code '
252 | . $response->code
253 | . ' but no data';
254 | }
255 | return $data;
256 | }
257 | else {
258 | # No idea what the server is telling me, must be high
259 | $data->{'cmd_status'} = 'NOTOK';
260 | $data->{'cmd_return'} =
261 | 'Server returned status code '
262 | . $response->code
263 | . ' which I don\'t understand';
264 | return $data;
265 | }
266 | }
267 | }
268 |
269 | 1;
270 |
271 | __END__
272 |
--------------------------------------------------------------------------------
/lib/Selenium/Remote/WDKeys.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Remote::WDKeys;
2 |
3 | # ABSTRACT: Representation of keystrokes used by Selenium::Remote::WebDriver
4 |
5 | =head1 DESCRIPTION
6 |
7 | The constant KEYS is defined here.
8 |
9 | =head1 SYNOPSIS
10 |
11 | use Selenium::Remote::WDKeys;
12 |
13 | my $space_key = KEYS->{'space'};
14 | my $enter_key = KEYS->{'enter'};
15 |
16 | =head1 CONSTANT KEYS
17 |
18 | null
19 | cancel
20 | help
21 | backspace
22 | tab
23 | clear
24 | return
25 | enter
26 | shift
27 | control
28 | alt
29 | pause
30 | escape
31 | space
32 | page_up
33 | page_down
34 | end
35 | home
36 | left_arrow
37 | up_arrow
38 | right_arrow
39 | down_arrow
40 | insert
41 | delete
42 | semicolon
43 | equals
44 | numpad_0
45 | numpad_1
46 | numpad_2
47 | numpad_3
48 | numpad_4
49 | numpad_5
50 | numpad_6
51 | numpad_7
52 | numpad_8
53 | numpad_9
54 | multiply
55 | add
56 | separator
57 | subtract
58 | decimal
59 | divide
60 | f1
61 | f2
62 | f3
63 | f4
64 | f5
65 | f6
66 | f7
67 | f8
68 | f9
69 | f10
70 | f11
71 | f12
72 | command_meta
73 | ZenkakuHankaku
74 |
75 | =head1 FUNCTIONS
76 |
77 | Functions of Selenium::Remote::WDKeys.
78 |
79 | =head2 KEYS
80 |
81 | my $keys = KEYS();
82 |
83 | A hash reference that contains constant keys. This function is exported by default.
84 |
85 | =cut
86 |
87 | use strict;
88 | use warnings;
89 |
90 | use base 'Exporter';
91 |
92 | # http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
93 | use constant KEYS => {
94 | 'null' => "\N{U+E000}",
95 | 'cancel' => "\N{U+E001}",
96 | 'help' => "\N{U+E002}",
97 | 'backspace' => "\N{U+E003}",
98 | 'tab' => "\N{U+E004}",
99 | 'clear' => "\N{U+E005}",
100 | 'return' => "\N{U+E006}",
101 | 'enter' => "\N{U+E007}",
102 | 'shift' => "\N{U+E008}",
103 | 'control' => "\N{U+E009}",
104 | 'alt' => "\N{U+E00A}",
105 | 'pause' => "\N{U+E00B}",
106 | 'escape' => "\N{U+E00C}",
107 | 'space' => "\N{U+E00D}",
108 | 'page_up' => "\N{U+E00E}",
109 | 'page_down' => "\N{U+E00f}",
110 | 'end' => "\N{U+E010}",
111 | 'home' => "\N{U+E011}",
112 | 'left_arrow' => "\N{U+E012}",
113 | 'up_arrow' => "\N{U+E013}",
114 | 'right_arrow' => "\N{U+E014}",
115 | 'down_arrow' => "\N{U+E015}",
116 | 'insert' => "\N{U+E016}",
117 | 'delete' => "\N{U+E017}",
118 | 'semicolon' => "\N{U+E018}",
119 | 'equals' => "\N{U+E019}",
120 | 'numpad_0' => "\N{U+E01A}",
121 | 'numpad_1' => "\N{U+E01B}",
122 | 'numpad_2' => "\N{U+E01C}",
123 | 'numpad_3' => "\N{U+E01D}",
124 | 'numpad_4' => "\N{U+E01E}",
125 | 'numpad_5' => "\N{U+E01f}",
126 | 'numpad_6' => "\N{U+E020}",
127 | 'numpad_7' => "\N{U+E021}",
128 | 'numpad_8' => "\N{U+E022}",
129 | 'numpad_9' => "\N{U+E023}",
130 | 'multiply' => "\N{U+E024}",
131 | 'add' => "\N{U+E025}",
132 | 'separator' => "\N{U+E026}",
133 | 'subtract' => "\N{U+E027}",
134 | 'decimal' => "\N{U+E028}",
135 | 'divide' => "\N{U+E029}",
136 | 'f1' => "\N{U+E031}",
137 | 'f2' => "\N{U+E032}",
138 | 'f3' => "\N{U+E033}",
139 | 'f4' => "\N{U+E034}",
140 | 'f5' => "\N{U+E035}",
141 | 'f6' => "\N{U+E036}",
142 | 'f7' => "\N{U+E037}",
143 | 'f8' => "\N{U+E038}",
144 | 'f9' => "\N{U+E039}",
145 | 'f10' => "\N{U+E03A}",
146 | 'f11' => "\N{U+E03B}",
147 | 'f12' => "\N{U+E03C}",
148 | 'command_meta' => "\N{U+E03D}",
149 | 'ZenkakuHankaku' => "\N{U+E040}", #Asian language keys, maybe altGr too?
150 | #There are other code points for say, left versus right meta/shift/alt etc, but I don't seriously believe anyone uses that level of sophistication on the web yet.
151 | };
152 |
153 | our @EXPORT = ('KEYS');
154 |
155 | 1;
156 |
--------------------------------------------------------------------------------
/lib/Selenium/Waiter.pm:
--------------------------------------------------------------------------------
1 | package Selenium::Waiter;
2 |
3 | use strict;
4 | use warnings;
5 |
6 | # ABSTRACT: Provides a utility wait_until function
7 | use Try::Tiny;
8 | require Exporter;
9 | our @ISA = qw/Exporter/;
10 | our @EXPORT = qw/wait_until/;
11 |
12 | =head1 SYNOPSIS
13 |
14 | use Selenium::Waiter qw/wait_until/;
15 | my $d = Selenium::Remote::Driver->new;
16 |
17 | my $div = wait_until { $d->find_element('div', 'css') };
18 |
19 | =head1 FUNCTIONS
20 |
21 | =head2 wait_until
22 |
23 | Exported by default, it takes a BLOCK (required) and optionally a
24 | hash of configuration params. It uses a prototype to take its
25 | arguments, so usage looks look like:
26 |
27 | use Selenium::Waiter;
28 | my $div = wait_until { $driver->find_element('div', 'css') };
29 |
30 | The above snippet will search for C for thirty seconds; if it
31 | ever finds the element, it will immediately return. More generally,
32 | Once the BLOCK returns anything truthy, the C will stop
33 | evaluating and the return of the BLOCK will be returned to you. If the
34 | BLOCK never returns a truthy value, we'll wait until the elapsed time
35 | has increased past the timeout and then return an empty string C<''>.
36 |
37 | B Please make sure that the BLOCK you pass in can be
38 | executed in a timely fashion. For Webdriver, that means that you
39 | should set the appropriate C timeout low (a second or
40 | less!) so that we can rerun the assert sub repeatedly. We don't do
41 | anything fancy behind the scenes: we just execute the BLOCK you pass
42 | in and sleep between iterations. If your BLOCK actively blocks for
43 | thirty seconds, like a C would do with an
44 | C of 30 seconds, we won't be able to help you at all -
45 | that blocking behavior is on the webdriver server side, and is out of
46 | our control. We'd run one iteration, get blocked for thirty seconds,
47 | and return control to you at that point.
48 |
49 | =head4 Dying
50 |
51 | PLEASE check the return value before proceeding, as we unwisely
52 | suppress any attempts your BLOCK may make to die or croak. The BLOCK
53 | you pass is called in a L, and if any of the
54 | invocations of your function throw and the BLOCK never becomes true,
55 | we'll carp exactly once at the end immediately before returning
56 | false. We overwrite the death message from each iteration, so at the
57 | end, you'll only see the most recent death message.
58 |
59 | # warns once after thirty seconds: "kept from dying";
60 | wait_until { die 'kept from dying' };
61 |
62 | The output of Cs from each iteration can be exposed if you wish
63 | to see the massacre:
64 |
65 | # carps: "kept from dying" once a second for thirty seconds
66 | wait_until { die 'kept from dying' } debug => 1;
67 |
68 | If you want to die anyways, just pass die => 1 to wait_until instead:
69 |
70 | # Dies on the first failure, do your own error handling:
71 | wait_until { die 'oops' } die => 1;
72 |
73 | =head4 Timeouts and Intervals
74 |
75 | You can also customize the timeout, and/or the retry interval between
76 | iterations.
77 |
78 | # prints hi three four times at 0, 3, 6, and 9 seconds
79 | wait_until { print 'hi'; '' } timeout => 10, interval => 3;
80 |
81 | =cut
82 |
83 | sub wait_until (&%) {
84 | my $assert = shift;
85 | my $args = {
86 | timeout => 30,
87 | interval => 1,
88 | debug => 0,
89 | die => 0,
90 | @_
91 | };
92 |
93 | my $start = time;
94 | my $timeout_not_elapsed = sub {
95 | my $elapsed = time - $start;
96 | return $elapsed < $args->{timeout};
97 | };
98 |
99 | my $exception = '';
100 | while ( $timeout_not_elapsed->() ) {
101 | my $assert_ret;
102 | my $try_ret = try {
103 | $assert_ret = $assert->();
104 | return $assert_ret if $assert_ret;
105 | }
106 | catch {
107 | $exception = $_;
108 | die $_ if $args->{die};
109 | warn $_ if $args->{debug};
110 | return '';
111 | }
112 | finally {
113 | if ( !$assert_ret ) {
114 | sleep( $args->{interval} );
115 | }
116 | };
117 |
118 | return $try_ret if $try_ret;
119 | }
120 |
121 | warn 'timeout' if $args->{debug};
122 |
123 | # No need to repeat ourselves if we're already debugging.
124 | warn $exception if $exception && !$args->{debug};
125 | return '';
126 | }
127 |
128 | 1;
129 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/Chrome.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::Chrome;
2 |
3 | use Moo;
4 | extends 'Selenium::Chrome', 'Test::Selenium::Remote::Driver';
5 |
6 | has 'webelement_class' => (
7 | is => 'rw',
8 | default => sub { 'Test::Selenium::Remote::WebElement' },
9 | );
10 |
11 | 1;
12 |
13 | __END__
14 |
15 | =head1 NAME
16 |
17 | Test::Selenium::Chrome
18 |
19 | =head1 SYNOPSIS
20 |
21 | my $test_driver = Test::Selenium::Chrome->new;
22 | $test_driver->get_ok('https://duckduckgo.com', "Chrome can load page");
23 | $test_driver->quit();
24 |
25 | =head1 DESCRIPTION
26 |
27 | A subclass of L which provides useful testing functions. Please see L and L for usage information.
28 |
29 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/Edge.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::Edge;
2 |
3 | use Moo;
4 | extends 'Selenium::Edge', 'Test::Selenium::Remote::Driver';
5 |
6 | has 'webelement_class' => (
7 | is => 'rw',
8 | default => sub { 'Test::Selenium::Remote::WebElement' },
9 | );
10 |
11 | 1;
12 |
13 | __END__
14 |
15 | =head1 NAME
16 |
17 | Test::Selenium::Edge
18 |
19 | =head1 SYNOPSIS
20 |
21 | my $test_driver = Test::Selenium::Edge->new;
22 | $test_driver->get_ok('https://duckduckgo.com', "MS Edge can load page");
23 | $test_driver->quit();
24 |
25 | =head1 DESCRIPTION
26 |
27 | A subclass of L which provides useful testing functions. Please see L and L for usage information.
28 |
29 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/Firefox.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::Firefox;
2 |
3 | use Moo;
4 | extends 'Selenium::Firefox', 'Test::Selenium::Remote::Driver';
5 |
6 | has 'webelement_class' => (
7 | is => 'rw',
8 | default => sub { 'Test::Selenium::Remote::WebElement' },
9 | );
10 |
11 | 1;
12 |
13 | __END__
14 |
15 | =head1 NAME
16 |
17 | Test::Selenium::Firefox
18 |
19 | =head1 SYNOPSIS
20 |
21 | my $test_driver = Test::Selenium::Firefox->new;
22 | $test_driver->get_ok('https://duckduckgo.com', "Firefox can load page");
23 | $test_driver->quit();
24 |
25 | =head1 DESCRIPTION
26 |
27 | A subclass of L which provides useful testing functions. Please see L and L for usage information.
28 |
29 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/InternetExplorer.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::InternetExplorer;
2 |
3 | use Moo;
4 | extends 'Selenium::InternetExplorer', 'Test::Selenium::Remote::Driver';
5 |
6 | has 'webelement_class' => (
7 | is => 'rw',
8 | default => sub { 'Test::Selenium::Remote::WebElement' },
9 | );
10 |
11 | 1;
12 |
13 | __END__
14 |
15 | =head1 NAME
16 |
17 | Test::Selenium::InternetExplorer
18 |
19 | =head1 SYNOPSIS
20 |
21 | my $test_driver = Test::Selenium::InternetExplorer->new;
22 | $test_driver->get_ok('https://duckduckgo.com', "InternetExplorer can load page");
23 | $test_driver->quit();
24 |
25 | =head1 DESCRIPTION
26 |
27 | A subclass of L which provides useful testing functions. Please see L and L for usage information.
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/PhantomJS.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::PhantomJS;
2 |
3 | use Moo;
4 | extends 'Selenium::PhantomJS', 'Test::Selenium::Remote::Driver';
5 |
6 | has 'webelement_class' => (
7 | is => 'rw',
8 | default => sub { 'Test::Selenium::Remote::WebElement' },
9 | );
10 |
11 | 1;
12 |
13 | __END__
14 |
15 | =head1 NAME
16 |
17 | Test::Selenium::PhantomJS
18 |
19 | =head1 SYNOPSIS
20 |
21 | my $test_driver = Test::Selenium::PhantomJS->new;
22 | $test_driver->get_ok('https://duckduckgo.com', "PhantomJS can load page");
23 | $test_driver->quit();
24 |
25 | =head1 DESCRIPTION
26 |
27 | A subclass of L which provides useful testing functions. Please see L and L for usage information.
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/Remote/Role/DoesTesting.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::Remote::Role::DoesTesting;
2 |
3 | # ABSTRACT: Role to cope with everything that is related to testing (could
4 | # be reused in both testing classes)
5 |
6 | use Moo::Role;
7 | use Test::Builder;
8 | use Try::Tiny;
9 | use Scalar::Util 'blessed';
10 | use List::Util qw/any/;
11 | use namespace::clean;
12 |
13 | requires qw(func_list has_args);
14 |
15 | has _builder => (
16 | is => 'lazy',
17 | builder => sub { return Test::Builder->new() },
18 | handles => [qw/is_eq isnt_eq like unlike ok croak/],
19 | );
20 |
21 | # get back the key value from an already coerced finder (default finder)
22 |
23 | sub _get_finder_key {
24 | my $self = shift;
25 | my $finder_value = shift;
26 |
27 | foreach my $k ( keys %{ $self->FINDERS } ) {
28 | return $k if ( $self->FINDERS->{$k} eq $finder_value );
29 | }
30 |
31 | return;
32 | }
33 |
34 | # main method for non ok tests
35 |
36 | sub _check_method {
37 | my $self = shift;
38 | my $method = shift;
39 | my $method_to_test = shift;
40 | $method = "get_$method";
41 | my @args = @_;
42 | my $rv;
43 | try {
44 | my $num_of_args = $self->has_args($method);
45 | my @r_args = splice( @args, 0, $num_of_args );
46 | $rv = $self->$method(@r_args);
47 | }
48 | catch {
49 | $self->croak($_);
50 | };
51 |
52 | return $self->$method_to_test( $rv, @args );
53 | }
54 |
55 | # main method for _ok tests
56 | # a bit hacked so that find_no_element_ok can also be processed
57 |
58 | sub _check_ok {
59 | my $self = shift;
60 | my $method = shift;
61 |
62 | my @args = @_;
63 | my ( $rv, $num_of_args, @r_args );
64 | try {
65 | $num_of_args = $self->has_args($method);
66 | @r_args = splice( @args, 0, $num_of_args );
67 | if ( $method =~ m/^find(_no|_child)?_element/ ) {
68 |
69 | # case find_element_ok was called with no arguments
70 | if ( scalar(@r_args) - $num_of_args == 1 ) {
71 | push @r_args, $self->_get_finder_key( $self->default_finder );
72 | }
73 | else {
74 | if ( scalar(@r_args) == $num_of_args ) {
75 |
76 | # case find_element was called with no finder but
77 | # a test description
78 | my $finder = $r_args[ $num_of_args - 1 ];
79 | my @FINDERS = keys( %{ $self->FINDERS } );
80 | unless ( any { $finder eq $_ } @FINDERS ) {
81 | $r_args[ $num_of_args - 1 ] =
82 | $self->_get_finder_key( $self->default_finder );
83 | push @args, $finder;
84 | }
85 | }
86 | }
87 | }
88 |
89 | # quick hack to fit 'find_no_element' into check_ok logic
90 | if ( $method eq 'find_no_element' ) {
91 | # If we use `find_element` and find nothing, the error
92 | # handler is incorrectly invoked. Doing a `find_elements`
93 | # and checking that it returns an empty array does not
94 | # invoke the error_handler. See
95 | # https://github.com/gempesaw/Selenium-Remote-Driver/issues/253
96 | my $elements = $self->find_elements(@r_args);
97 | if ( @{$elements} ) {
98 | $rv = $elements->[0];
99 | }
100 | else {
101 | $rv = 1; # empty list means success
102 | }
103 | }
104 | else {
105 | $rv = $self->$method(@r_args); # a true $rv means success
106 | }
107 | }
108 | catch {
109 | if ($method eq 'find_no_element') {
110 | $rv = 1; # an exception from find_elements() means success
111 | }
112 | else {
113 | $self->croak($_);
114 | }
115 | };
116 |
117 | # test description might have been explicitly passed
118 | my $test_name = pop @args;
119 |
120 | # generic test description when no explicit test description was passed
121 | if ( ! defined $test_name ) {
122 | $test_name = $num_of_args > 0 ?
123 | join( ' ', $method, map { q{'$_'} } @r_args )
124 | :
125 | $method;
126 | }
127 |
128 | # case when find_no_element found an element, we should croak
129 | if ( $method eq 'find_no_element' ) {
130 | if ( blessed($rv) && $rv->isa('Selenium::Remote::WebElement') ) {
131 | $self->croak($test_name);
132 | }
133 | }
134 |
135 | return $self->ok( $rv, $test_name );
136 | }
137 |
138 | # build the subs with the correct arg set
139 |
140 | sub _build_sub {
141 | my $self = shift;
142 | my $meth_name = shift;
143 |
144 | # e.g. for $meth_name = 'find_no_element_ok':
145 | # $meth_comp = 'ok'
146 | # $meth_without_comp = 'find_no_element'
147 | my @meth_elements = split '_', $meth_name;
148 | my $meth_comp = pop @meth_elements;
149 | my $meth_without_comp = join '_', @meth_elements;
150 |
151 | # handle the ok testing methods
152 | if ( $meth_comp eq 'ok' ) {
153 | return sub {
154 | my $self = shift;
155 |
156 | local $Test::Builder::Level = $Test::Builder::Level + 2;
157 |
158 | return $self->_check_ok($meth_without_comp, @_);
159 | };
160 | }
161 |
162 | # find the Test::More comparator method
163 | my %comparators = (
164 | is => 'is_eq',
165 | isnt => 'isnt_eq',
166 | like => 'like',
167 | unlike => 'unlike',
168 | );
169 |
170 | # croak on unknown comparator methods
171 | if ( ! exists $comparators{$meth_comp} ) {
172 | return sub {
173 | my $self = shift;
174 |
175 | return $self->croak("Sub $meth_name could not be defined");
176 | };
177 | }
178 |
179 | # handle check in _check_method()
180 | return sub {
181 | my $self = shift;
182 |
183 | local $Test::Builder::Level = $Test::Builder::Level + 2;
184 |
185 | return $self->_check_method( $meth_without_comp, $comparators{$meth_comp}, @_ );
186 | };
187 | }
188 |
189 | 1;
190 |
191 | =head1 NAME
192 |
193 | Selenium::Remote::Role::DoesTesting - Role implementing the common logic used for testing
194 |
195 | =cut
196 |
--------------------------------------------------------------------------------
/lib/Test/Selenium/Remote/WebElement.pm:
--------------------------------------------------------------------------------
1 | package Test::Selenium::Remote::WebElement;
2 |
3 | # ABSTRACT: A sub-class of L, with several test-specific method additions.
4 |
5 | use Moo;
6 | use Sub::Install;
7 | extends 'Selenium::Remote::WebElement';
8 |
9 | =for Pod::Coverage *EVERYTHING*
10 |
11 | =cut
12 |
13 | # list of test functions to be built
14 |
15 | has func_list => (
16 | is => 'lazy',
17 | builder => sub {
18 | return [
19 | 'clear_ok', 'click_ok', 'send_keys_ok',
20 | 'is_displayed_ok', 'is_enabled_ok', 'is_selected_ok',
21 | 'submit_ok', 'text_is', 'text_isnt',
22 | 'text_like', 'text_unlike', 'attribute_is',
23 | 'attribute_isnt', 'attribute_like', 'attribute_unlike',
24 | 'value_is', 'value_isnt', 'value_like',
25 | 'value_unlike', 'tag_name_is', 'tag_name_isnt',
26 | 'tag_name_like', 'tag_name_unlike'
27 | ];
28 | }
29 | );
30 |
31 | with 'Test::Selenium::Remote::Role::DoesTesting';
32 |
33 | # helper so we could specify the num of args a method takes (if any)
34 |
35 | sub has_args {
36 | my $self = shift;
37 | my $fun_name = shift;
38 | my $hash_fun_args = {
39 | 'get_attribute' => 1,
40 | 'send_keys' => 1,
41 | };
42 | return ( $hash_fun_args->{$fun_name} // 0 );
43 | }
44 |
45 | # install the test methods into the class namespace
46 |
47 | sub BUILD {
48 | my $self = shift;
49 | foreach my $method_name ( @{ $self->func_list } ) {
50 | unless ( defined( __PACKAGE__->can($method_name) ) ) {
51 | my $sub = $self->_build_sub($method_name);
52 | Sub::Install::install_sub(
53 | {
54 | code => $sub,
55 | into => __PACKAGE__,
56 | as => $method_name
57 | }
58 | );
59 | }
60 | }
61 | }
62 |
63 | 1;
64 |
65 | __END__
66 |
67 | =head1 DESCRIPTION
68 |
69 | This is an I addition to the Selenium::Remote::Driver
70 | distribution, and some interfaces may change.
71 |
72 | =head1 METHODS
73 |
74 | All methods from L are available through this
75 | module, as well as the following test-specific methods. All test names are optional.
76 |
77 | text_is($match_str,$test_name);
78 | text_isnt($match_str,$test_name);
79 | text_like($match_re,$test_name);
80 | text_unlike($match_re,$test_name);
81 |
82 | tag_name_is($match_str,$test_name);
83 | tag_name_isnt($match_str,$test_name);
84 | tag_name_like($match_re,$test_name);
85 | tag_name_unlike($match_re,$test_name);
86 |
87 | value_is($match_str,$test_name);
88 | value_isnt($match_str,$test_name);
89 | value_like($match_re,$test_name);
90 | value_unlike($match_re,$test_name);
91 |
92 | clear_ok($test_name);
93 | click_ok($test_name);
94 | submit_ok($test_name);
95 | is_selected_ok($test_name);
96 | is_enabled_ok($test_name);
97 | is_displayed_ok($test_name);
98 |
99 | send_keys_ok($str)
100 | send_keys_ok($str,$test_name)
101 |
102 | attribute_is($attr_name,$match_str,$test_name);
103 | attribute_isnt($attr_name,$match_str,$test_name);
104 | attribute_like($attr_name,$match_re,$test_name);
105 | attribute_unlike($attr_name,$match_re,$test_name);
106 |
107 | css_attribute_is($attr_name,$match_str,$test_name); # TODO
108 | css_attribute_isnt($attr_name,$match_str,$test_name); # TODO
109 | css_attribute_like($attr_name,$match_re,$test_name); # TODO
110 | css_attribute_unlike($attr_name,$match_re,$test_name); # TODO
111 |
112 | element_location_is([x,y]) # TODO
113 | element_location_in_view_is([x,y]) # TODO
114 |
115 | =head1 AUTHORS
116 |
117 | =over 4
118 |
119 | =item *
120 |
121 | Created by: Mark Stosberg , but inspired by
122 | L and its authors.
123 |
124 | =back
125 |
126 | =head1 COPYRIGHT AND LICENSE
127 |
128 | Copyright (c) 2013 Mark Stosberg
129 |
130 | This program is free software; you can redistribute it and/or
131 | modify it under the same terms as Perl itself.
132 |
--------------------------------------------------------------------------------
/perlcriticrc:
--------------------------------------------------------------------------------
1 | exclude = RequireUseStrict|RequireUseWarnings|ProhibitSubroutinePrototypes
2 |
--------------------------------------------------------------------------------
/t/00-load.t:
--------------------------------------------------------------------------------
1 |
2 | use strict;
3 | use warnings;
4 | use Test::More;
5 |
6 | BEGIN {
7 | use_ok( 'Selenium::Remote::Driver' ) || print "Bail out!";
8 | use_ok( 'Test::Selenium::Remote::Driver' ) || print "Bail out!";
9 | use_ok('Selenium::Remote::Driver::Firefox::Profile') || print "Bail out!";
10 | }
11 |
12 | done_testing;
13 |
--------------------------------------------------------------------------------
/t/01-driver-pac.t:
--------------------------------------------------------------------------------
1 | #! /usr/bin/perl
2 |
3 | use strict;
4 | use warnings;
5 | use JSON;
6 | use Selenium::Remote::Driver;
7 | use Test::More;
8 | use Test::Fatal;
9 | use Test::LWP::UserAgent;
10 |
11 | my $croaking_tests = [
12 | {
13 | name => 'no PAC url',
14 | proxy => {
15 | proxyType => 'pac',
16 | },
17 | pattern => qr/not provided/,
18 | },
19 | {
20 | name => 'PAC url is not http or file',
21 | proxy => {
22 | proxyType => 'pac',
23 | proxyAutoconfigUrl => ''
24 | },
25 | pattern => qr{of format http:// or file://}
26 | }
27 | ];
28 |
29 | foreach my $test (@$croaking_tests) {
30 | like(
31 | exception {
32 | Selenium::Remote::Driver->new(proxy => $test->{proxy});
33 | },
34 | $test->{pattern},
35 | 'Coercion croaks for case: ' . $test->{name}
36 | );
37 | }
38 |
39 | my $passing_tests = [
40 | {
41 | name => 'PAC url is http',
42 | proxy => {
43 | proxyType => 'pac',
44 | proxyAutoconfigUrl => 'http://pac.file'
45 | }
46 | },
47 | {
48 | name => 'PAC url is file',
49 | proxy => {
50 | proxyType => 'pac',
51 | proxyAutoconfigUrl => 'file://' . __FILE__
52 | }
53 | }
54 | ];
55 |
56 | my $tua = mock_simple_webdriver_server();
57 | foreach my $test (@$passing_tests) {
58 | is(
59 | exception {
60 | Selenium::Remote::Driver->new(
61 | proxy => $test->{proxy},
62 | ua => $tua
63 | );
64 | },
65 | undef,
66 | 'Coercion passes for case: ' . $test->{name}
67 | );
68 | }
69 |
70 | sub mock_simple_webdriver_server {
71 | my $tua = Test::LWP::UserAgent->new;
72 | $tua->map_response(qr/status/, HTTP::Response->new(200, 'OK'));
73 | $tua->map_response(
74 | qr/session/,
75 | HTTP::Response->new(
76 | 204,
77 | 'OK',
78 | ['Content-Type' => 'application/json'],
79 | to_json({
80 | cmd_return => {},
81 | cmd_status => 'OK',
82 | sessionId => '123123123'
83 | })
84 | )
85 | );
86 |
87 | return $tua;
88 | }
89 |
90 | done_testing;
91 |
--------------------------------------------------------------------------------
/t/02-webelement.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::More;
5 | use Test::MockModule v0.13;
6 | use Selenium::Remote::Driver;
7 | use Selenium::Remote::Mock::RemoteConnection;
8 |
9 | use FindBin;
10 | use lib $FindBin::Bin . '/lib';
11 | use TestHarness;
12 |
13 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
14 |
15 | my $harness = TestHarness->new(
16 | this_file => $FindBin::Script
17 | );
18 | my %selenium_args = %{ $harness->base_caps };
19 |
20 | my $driver = Selenium::Remote::Driver->new(%selenium_args);
21 | my $domain = $harness->domain;
22 | my $website = $harness->website;
23 |
24 | $driver->get("$website/formPage.html");
25 | my $ret;
26 | my $elem;
27 |
28 | LINK: {
29 | $driver->find_element("//a[\@href='/index.html']")->click;
30 | pass('Click Link...');
31 | isa_ok($driver->get_active_element,"Selenium::Remote::WebElement","get_active_element");
32 | $ret = $driver->get_title();
33 | is($ret, 'Hello WebDriver', 'Verify clicked link.');
34 | $driver->go_back();
35 | }
36 |
37 | INPUT: {
38 | $elem = $driver->find_element('withText', 'id');
39 | $ret = $elem->get_text();
40 | is($ret, 'Example text', 'Get innerText');
41 | $elem = $driver->find_element('id-name1', 'id');
42 | $ret = $elem->get_value();
43 | is($ret, 'id', 'Get value (attribute)');
44 | $ret = $elem->get_attribute('value');
45 | is($ret, 'id', 'Get attribute @value');
46 | $ret = $elem->get_attribute('missing-attribute');
47 | ok(!$ret, 'Get attribute returns false for a missing attribute.');
48 | $ret = $elem->get_tag_name();
49 | is($ret, 'input', 'Get tag name');
50 | my $selfmock = Test::MockModule->new('Selenium::Remote::WebElement');
51 | $selfmock->mock('get_tag_name',sub { 'input' });
52 | $selfmock->mock('get_property',sub { 0 });
53 |
54 | $elem = $driver->find_element('checky', 'id');
55 | $ret = $elem->is_selected();
56 | is($ret, 0, 'Checkbox not selected');
57 | $ret = $elem->click();
58 | $selfmock->mock('get_property',sub { 1 });
59 |
60 | $ret = $elem->is_selected();
61 | is($ret, 1, 'Checkbox is selected');
62 | TODO: {
63 | local $TODO = "toggle doesn't appear to be working currently in selenium server";
64 | eval {$ret = $elem->toggle();};
65 | $ret = $elem->is_selected();
66 | is($ret, 0, 'Toggle & Checkbox is selected');
67 | }
68 | }
69 |
70 | MODIFIER: {
71 | $driver->get("$website/metakeys.html");
72 | $elem = $driver->find_element('metainput','id');
73 | eval {
74 | $driver->send_modifier('Alt','down');
75 | $elem->send_keys('c');
76 | $driver->send_modifier('Alt','up');
77 | };
78 | if ($@) {
79 | TODO: {
80 | local $TODO = "modifier keys broken case 1993 and 1427";
81 | fail "sent modifier keys";
82 | }
83 | }
84 | else {
85 | $elem = $driver->find_element('metaoutput','id');
86 | like($elem->get_value,qr/18/,"sent modifier keys");
87 | note $elem->get_value;
88 | }
89 | }
90 |
91 | IMAGES: {
92 | $driver->get("$website/dragAndDropTest.html");
93 | $elem = $driver->find_element('test1', 'id');
94 | $ret = $elem->get_size();
95 | is($ret->{'width'}, '18', 'Image - right width');
96 | is($ret->{'height'}, '18', 'Image - right height');
97 | $ret = $elem->get_element_location();
98 | ok(defined $ret->{'x'}, 'Image - got x coord');
99 | ok(defined $ret->{'y'}, 'Image - got y coord');
100 | my $x = $ret->{'x'};
101 | my $y = $ret->{'y'};
102 | }
103 |
104 | VISIBILITY: {
105 | $driver->get("$website/index.html");
106 | $elem = $driver->find_element('displayed', 'id');
107 | ok($elem->is_displayed(), 'Elements are displayed by default.');
108 | ok(!$elem->is_hidden(), 'Elements are not hidden by default.');
109 |
110 | $elem = $driver->find_element('hidden', 'id');
111 | ok(!$elem->is_displayed(), 'Hidden elements are not displayed.');
112 | ok($elem->is_hidden(), 'Hidden elements are hidden.');
113 | }
114 |
115 | EXECUTE_SCRIPT: {
116 | $driver->get("$website/index.html");
117 |
118 | my $elem = $driver->find_element('div', 'css');
119 | my $script_elem = $driver->execute_script('return arguments[0]', $elem);
120 | isa_ok($script_elem, 'Selenium::Remote::WebElement', 'execute_script element return');
121 | is($elem->id, $script_elem->id, 'Sync script returns identical WebElement id');
122 |
123 | my $async = q{
124 | var callback = arguments[arguments.length - 1];
125 | callback(arguments[0]);
126 | };
127 | my $async_elem = $driver->execute_async_script($async, $elem);
128 | isa_ok($async_elem, 'Selenium::Remote::WebElement', 'execute_async_script element return');
129 | is($elem->id, $async_elem->id, 'Async script returns identical WebElement id');
130 | }
131 |
132 | QUIT: {
133 | $ret = $driver->quit();
134 | ok((not defined $driver->{'session_id'}), 'Killed the remote session');
135 | }
136 |
137 | OBJECT_INSTANTIATION: {
138 | SRD: {
139 | my $value = { ELEMENT => 0 };
140 | my $elem = Selenium::Remote::WebElement->new(
141 | id => $value,
142 | driver => ''
143 | );
144 | is($elem->id, 0,
145 | 'Can make element with standard SRD response');
146 | }
147 |
148 | GECKODRIVER:{
149 | my $value = {
150 | 'element-6066-11e4-a52e-4f735466cecf' => '4f134cd0-4873-1148-aac8-5d496bea013f'
151 | };
152 | my $elem = Selenium::Remote::WebElement->new(
153 | id => $value,
154 | driver => ''
155 | );
156 | is($elem->id, '4f134cd0-4873-1148-aac8-5d496bea013f',
157 | 'Can make element with Geckodriver response');
158 |
159 | }
160 | }
161 |
162 | done_testing;
163 |
--------------------------------------------------------------------------------
/t/03-spec-coverage.t:
--------------------------------------------------------------------------------
1 |
2 | use strict;
3 | use warnings;
4 |
5 | use LWP::UserAgent;
6 | use Selenium::Remote::Commands;
7 | use Test::More;
8 |
9 | unless($ENV{RELEASE_TESTING}) {
10 | plan(skip_all=>"Author tests not required for installation.");
11 | }
12 |
13 | my $uri = "http://selenium.googlecode.com/svn/wiki/JsonWireProtocol.wiki";
14 | my $ua = LWP::UserAgent->new;
15 | my $data = $ua->get($uri)->content;
16 | plan skip_all => "need internet connection to run spec test" if !$data;
17 |
18 | my $todo_list = {
19 | 'GET session/:sessionId/orientation' => 1,
20 | 'POST session/:sessionId/orientation' => 1,
21 | 'POST session/:sessionId/ime/deactivate' => 1,
22 | 'GET session/:sessionId/ime/activated' => 1,
23 | 'POST session/:sessionId/ime/activate' => 1,
24 | 'GET session/:sessionId/ime/active_engine' => 1,
25 | 'GET session/:sessionId/ime/available_engines' => 1,
26 | 'POST session/:sessionId/touch/click' => 1,
27 | 'POST session/:sessionId/touch/down' => 1,
28 | 'POST session/:sessionId/touch/up' => 1,
29 | 'GET sessions' => 1,
30 | 'POST session/:sessionId/window/:windowHandle/size' => 1,
31 | 'GET session/:sessionId/window/:windowHandle/size' => 1,
32 | 'POST session/:sessionId/window/:windowHandle/position' => 1,
33 | 'GET session/:sessionId/window/:windowHandle/position' => 1,
34 | 'POST session/:sessionId/keys' => 1,
35 | 'GET session/:sessionId/location' => 1,
36 | 'POST session/:sessionId/location' => 1,
37 | 'POST session/:sessionId/window/:windowHandle/maximize' => 1,
38 | 'GET session/:sessionId/local_storage' => 1,
39 | 'POST session/:sessionId/local_storage' => 1,
40 | 'DELETE session/:sessionId/local_storage' => 1,
41 | 'GET session/:sessionId/local_storage/key/:key' => 1,
42 | 'DELETE session/:sessionId/local_storage/key/:key' => 1,
43 | 'GET session/:sessionId/local_storage/size' => 1,
44 | 'GET session/:sessionId/session_storage' => 1,
45 | 'POST session/:sessionId/session_storage' => 1,
46 | 'DELETE session/:sessionId/session_storage' => 1,
47 | 'GET session/:sessionId/session_storage/key/:key' => 1,
48 | 'DELETE session/:sessionId/session_storage/key/:key' => 1,
49 | 'GET session/:sessionId/session_storage/size' => 1,
50 | };
51 | my @lines = split(/\n/, $data);
52 | my @methods;
53 |
54 | for my $line (@lines) {
55 | if ($line =~
56 | /\|\|\s*(GET|POST|DELETE)\s*\|\|\s*\[\S*\s+\/([^\]]*)\]\s*\|\|\s*([^\|]*?)\s*\|\|/
57 | ) {
58 | my $method = {method => $1, path => $2, desc => $3};
59 | push @methods, $method;
60 | }
61 | }
62 | my $commands = Selenium::Remote::Commands->new->get_cmds;
63 | SOURCE_COMMAND: for my $method_source (@methods) {
64 | my $command = "$method_source->{method} $method_source->{path}";
65 | my $msg = "Looking for '$command'";
66 | for my $method_local (values %{$commands}) {
67 | if ( $method_local->{url} eq $method_source->{path}
68 | and $method_local->{method} eq $method_source->{method}) {
69 | pass($msg);
70 | next SOURCE_COMMAND;
71 | }
72 | }
73 | TODO: {
74 | local $TODO = "need to create command" if $todo_list->{$command};
75 | fail($msg);
76 | diag("Add this to lib/Selenium/Remote/Commands.pm:
77 |
78 | # '$method_source->{path}' => {
79 | # 'method' => '$method_source->{method}',
80 | # 'url' => '$method_source->{path}'
81 | # },
82 |
83 | or add this to t/03-spec-coverage.t, to the hash %todo_list:
84 |
85 | # '$command' => 1,
86 |
87 | \n");
88 | }
89 | }
90 |
91 | LOCAL_COMMAND: for my $method_local (values %{$commands}) {
92 | my $msg = "extra command $method_local->{method} $method_local->{url}";
93 | for my $method_source (@methods) {
94 | if ( $method_local->{url} eq $method_source->{path}
95 | and $method_local->{method} eq $method_source->{method}) {
96 | next LOCAL_COMMAND;
97 | }
98 | }
99 | TODO: {
100 | local $TODO = "Investigate extra methods";
101 | fail($msg);
102 | }
103 | }
104 |
105 | done_testing;
106 |
--------------------------------------------------------------------------------
/t/04-commands-implemented.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | # TODO: find another way to do this checking, this is so fragile
5 | use Selenium::Remote::Commands;
6 | use Selenium::Remote::Spec;
7 | use Test::More;
8 |
9 | unless($ENV{RELEASE_TESTING}) {
10 | plan(skip_all=>"Author tests not required for installation.");
11 | }
12 |
13 | my $comm = Selenium::Remote::Commands->new->get_cmds;
14 | for my $command (keys %{$comm}) {
15 | my $found_command = 0;
16 | for my $file (
17 | qw{lib/Selenium/Remote/Driver.pm
18 | lib/Selenium/Remote/WebElement.pm
19 | lib/Selenium/Firefox.pm}
20 | ) {
21 | open(my $fh, '<', $file) or die "Couldn't open file $file";
22 | for (<$fh>) {
23 | if (/'?command'?\s*=>\s*'$command'/
24 | or /{'?commands'?}->\{'?$command'?}/) {
25 | pass("find $command");
26 | $found_command = 1;
27 | }
28 | }
29 | }
30 | if (!$found_command && $command !~ /Gecko|screenshot/i) {
31 | fail("find $command");
32 | }
33 | }
34 |
35 | note "*************** WD3 methods ********************";
36 | $comm = Selenium::Remote::Spec->new->get_cmds;
37 | for my $command (keys %{$comm}) {
38 | my $found_command = 0;
39 | for my $file (
40 | qw{lib/Selenium/Remote/Driver.pm
41 | lib/Selenium/Remote/WebElement.pm
42 | lib/Selenium/Firefox.pm}
43 | ) {
44 | open(my $fh, '<', $file) or die "Couldn't open file $file";
45 | for (<$fh>) {
46 | if (/'?command'?\s*=>\s*'$command'/
47 | or /{'?commands'?}->\{'?$command'?}/) {
48 | pass("find $command");
49 | $found_command = 1;
50 | }
51 | }
52 | }
53 | if (!$found_command && $command !~ /Gecko|screenshot/i) {
54 | fail("find $command");
55 | }
56 | }
57 |
58 | done_testing;
59 |
60 | 1;
61 |
--------------------------------------------------------------------------------
/t/10-switch-to-window.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::More;
5 | use Test::Selenium::Remote::Driver;
6 | use Selenium::Remote::Mock::RemoteConnection;
7 |
8 | use FindBin;
9 | use lib $FindBin::Bin . '/lib';
10 | use Test::MockModule;
11 | use TestHarness;
12 |
13 | my $harness = TestHarness->new(
14 | this_file => $FindBin::Script
15 | );
16 |
17 | my @browsers = qw/chrome/;
18 |
19 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
20 |
21 | my @sessions = qw{897bfa82-0f28-4875-8544-5cc02e8b82f6};
22 | my $mock = Test::MockModule->new('Selenium::Remote::Driver');
23 | $mock->mock('new_session', sub { my $s = shift; $s->{session_id} = shift @sessions } );
24 |
25 | foreach (@browsers) {
26 | my %selenium_args = (
27 | default_finder => 'css',
28 | javascript => 1,
29 | %{ $harness->base_caps },
30 | browser_name => $_,
31 | );
32 |
33 | my $s = Test::Selenium::Remote::Driver->new(
34 | %selenium_args
35 | );
36 |
37 | my $perl_title = 'The Perl Programming Language - www.perl.org';
38 | my $cpan_title = 'The Comprehensive Perl Archive Network - www.cpan.org';
39 |
40 | $s->get_ok('http://perl.org/');
41 | $s->title_is($perl_title, 'perl.org title matches correctly');
42 | my $old_handles = $s->get_window_handles;
43 | is(scalar(@$old_handles), 1, 'got one window handle as expected');
44 | my $perl_handle = $old_handles->[0];
45 | # setting the window.name manually
46 | $s->execute_script(q{return window.name = 'perlorg';});
47 |
48 | # setting the window.name when opening the other window
49 | $s->execute_script(q{$(window.open('http://cpan.org/', 'cpanorg'))});
50 | $s->title_is($perl_title, 'creating a new window keeps us focused on the current window');
51 |
52 | my $handles = $s->get_window_handles;
53 | is(scalar(@$handles), 2, 'get_window_handles sees both of our browser windows');
54 | # We don't assume any order in the @$handles array:
55 | my $cpan_handle = $perl_handle eq $handles->[0] ? $handles->[1] : $handles->[0];
56 |
57 | $s->switch_to_window($cpan_handle);
58 | $s->get_ok('http://cpan.org');
59 | $s->title_is($cpan_title, 'can switch to window by handle');
60 |
61 | $s->switch_to_window($perl_handle);
62 | $s->title_is($perl_title, 'can switch back to main window by handle');
63 |
64 | if ($_ eq 'chrome') {
65 | $s->switch_to_window('cpanorg');
66 | $s->title_is($cpan_title, 'can switch to window by window title in chrome');
67 |
68 | $s->switch_to_window('perlorg');
69 | $s->title_is($perl_title, 'can switch to main window by window title in chrome');
70 | }
71 | }
72 |
73 | done_testing;
74 |
--------------------------------------------------------------------------------
/t/12-reuse-session.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::More;
5 | use Test::MockModule;
6 | use Test::Selenium::Remote::Driver;
7 | use Selenium::Remote::Mock::RemoteConnection;
8 |
9 | use FindBin;
10 | use lib $FindBin::Bin . '/lib';
11 | use TestHarness;
12 |
13 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
14 |
15 | my $harness = TestHarness->new(
16 | this_file => $FindBin::Script
17 | );
18 |
19 | my @browsers = qw/chrome/;
20 |
21 | foreach (@browsers) {
22 | my @mock_session_ids = qw{2257c1cf-17d9-401a-a13b-fc7a279d7db5 dddddddd-17d9-401a-a13b-fc7a279d7db5 17c83f3a-3f23-4ffc-a50f-06ba5f5202d1};
23 |
24 | my $mock = Test::MockModule->new('Selenium::Remote::Driver');
25 | $mock->mock('new_session', sub { my $s = shift; $s->{session_id} //= shift @mock_session_ids } );
26 |
27 |
28 | my %selenium_args = (
29 | default_finder => 'css',
30 | javascript => 1,
31 | %{ $harness->base_caps },
32 | browser_name => $_,
33 | );
34 |
35 | my $s1 = Test::Selenium::Remote::Driver->new(
36 | %selenium_args
37 | );
38 | my $s2 = Test::Selenium::Remote::Driver->new(
39 | %selenium_args,
40 | auto_close => 0,
41 | session_id => $s1->session_id,
42 | );
43 |
44 | my $s3 = Test::Selenium::Remote::Driver->new(
45 | %selenium_args,
46 | );
47 |
48 | is($s1->session_id, $s2->session_id, "session_id is reused when specified");
49 | isnt($s1->session_id, $s3->session_id, "session_id not reused");
50 | pass("session_id.1=". $s2->session_id);
51 | pass("session_id.2=". $s2->session_id);
52 | pass("session_id.3=". $s3->session_id);
53 |
54 | my $perl_title = 'The Perl Programming Language - www.perl.org';
55 | my $cpan_title = 'The Comprehensive Perl Archive Network - www.cpan.org';
56 |
57 | $s1->get_ok('http://perl.org/');
58 | $s1->title_is($perl_title, 'perl.org title matches correctly');
59 |
60 | $s3->get_ok('http://perl.org/');
61 | $s3->title_is($perl_title, 'perl.org title matches correctly');
62 | }
63 |
64 | done_testing;
65 |
--------------------------------------------------------------------------------
/t/13-waiter.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Selenium::Waiter;
5 |
6 | use FindBin;
7 | use lib $FindBin::Bin . '/lib';
8 | use Test::More;
9 |
10 | my $res;
11 |
12 | subtest 'basic' => sub {
13 | my @warning;
14 | local $SIG{__WARN__} = sub { push( @warning, $_[0] ) };
15 |
16 | $res = wait_until { 1 };
17 | is $res, 1, 'right return value';
18 |
19 | $res = wait_until { 0 } timeout => 1;
20 | is $res, '', 'right return value';
21 |
22 | is( scalar @warning, 0, 'no warnings' );
23 | };
24 |
25 | subtest 'exception' => sub {
26 | my @warning;
27 | local $SIG{__WARN__} = sub { push( @warning, $_[0] ) };
28 |
29 | $res = wait_until { die 'case1' } debug => 0, timeout => 1;
30 | is $res, '', 'right return value';
31 | is( scalar @warning, 1, 'right number of warnings' );
32 | like( $warning[0], qr{^case1}, 'right warning' );
33 |
34 | @warning = ();
35 | eval {
36 | $res = wait_until { die 'case2' } die => 1, timeout => 1;
37 | };
38 | like $@, qr{case2}, 'right error';
39 | is $res, '', 'right return value';
40 | is( scalar @warning, 0, 'right number of warnings' );
41 |
42 | @warning = ();
43 | $res = wait_until { 0 } debug => 1, timeout => 1;
44 | is $res, '', 'right return value';
45 | is( scalar @warning, 1, 'right number of warnings' );
46 | like( $warning[0], qr{timeout}i, 'timeout is reported' );
47 | };
48 |
49 | done_testing;
50 |
--------------------------------------------------------------------------------
/t/CanSetWebdriverContext.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Test::More;
4 |
5 | {
6 | package SetWebdriverContext;
7 | use Moo;
8 | with 'Selenium::Remote::Driver::CanSetWebdriverContext';
9 |
10 | }
11 |
12 | my $prefix = SetWebdriverContext->new;
13 | ok($prefix->can('wd_context_prefix'), 'role grants wd context prefix attr');
14 | is($prefix->wd_context_prefix, '/wd/hub', 'role has proper default webdriver context');
15 |
16 | done_testing;
17 |
--------------------------------------------------------------------------------
/t/Finders.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Test::More;
4 | use Selenium::Remote::Driver;
5 | use FindBin;
6 | use lib $FindBin::Bin . '/lib';
7 | use TestHarness;
8 |
9 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
10 |
11 | my $harness = TestHarness->new(
12 | this_file => $FindBin::Script
13 | );
14 |
15 | my %selenium_args = %{ $harness->base_caps };
16 |
17 | my $driver = Selenium::Remote::Driver->new(%selenium_args);
18 | $driver->get('http://danielgempesaw.com/Selenium-Remote-Driver/xhtmlTest.html');
19 |
20 | # This depends explicitly on the page we're visiting (xhtmlTest.html),
21 | my %finders = (
22 | class => 'navigation',
23 | class_name => 'navigation',
24 | css => 'html',
25 | id => 'linkId',
26 | link => 'this goes to the same place',
27 | link_text => 'this goes to the same place',
28 | name => 'windowOne',
29 | partial_link_text => 'this goes to the same',
30 | tag_name => 'html',
31 | xpath => '//html'
32 | );
33 |
34 | foreach my $by (keys %finders) {
35 | my $locator = $finders{$by};
36 | my $method = 'find_element_by_' . $by;
37 |
38 | ok($driver->can($method), $method . ': installed properly');
39 | my $elem = $driver->$method($locator);
40 | ok($elem, $method . ': finds an element properly');
41 | ok($elem->isa('Selenium::Remote::WebElement'), $method . ': element is a WebElement');
42 | {
43 | # Briefly suppress warning output for prettier tests
44 | my $warned = 0;
45 | local $SIG{__WARN__} = sub { $warned++ };
46 | ok(!$driver->$method('missing') , $method . ': does not croak on unavailable elements');
47 | ok($warned, $method . ': unavailable elements throw a warning');
48 | }
49 | }
50 | done_testing;
51 |
--------------------------------------------------------------------------------
/t/Firefox-Profile.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Selenium::Remote::Driver;
5 | use Test::More;
6 |
7 | use MIME::Base64 qw/decode_base64/;
8 | use IO::Uncompress::Unzip qw(unzip $UnzipError);
9 | use File::Temp;
10 | use JSON;
11 | use Selenium::Remote::Mock::RemoteConnection;
12 | use Selenium::Firefox::Profile;
13 |
14 | use FindBin;
15 | use lib $FindBin::Bin . '/lib';
16 | use TestHarness;
17 |
18 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
19 |
20 | my $harness = TestHarness->new(
21 | this_file => $FindBin::Script
22 | );
23 | my %selenium_args = %{ $harness->base_caps };
24 |
25 | my $fixture_dir = $FindBin::Bin . '/www/';
26 |
27 | CUSTOM_EXTENSION_LOADED: {
28 | my $profile = Selenium::Firefox::Profile->new;
29 | my $domain = $harness->domain;
30 | my $website = $harness->website;
31 | my $mock_encoded_profile = $fixture_dir . 'encoded_profile.b64';
32 | my $encoded;
33 |
34 | # Set this to true to re-encode the profile. This should not need
35 | # to happen often.
36 | my $create_new_profile = 0;
37 | if ($create_new_profile) {
38 | $profile->set_preference(
39 | 'browser.startup.homepage' => $website
40 | );
41 |
42 | # This extension rewrites any page url to a single . The
43 | # following javascript is in redisplay.xpi's
44 | # resources/gempesaw/lib/main.js:
45 |
46 | # var pageMod = require("sdk/page-mod");
47 | # pageMod.PageMod({
48 | # include: "*",
49 | # contentScript: 'document.body.innerHTML = ' +
50 | # ' "Page matches ruleset ";'
51 | # });
52 | $profile->add_extension($fixture_dir . 'redisplay.xpi');
53 |
54 | $encoded = $profile->_encode;
55 |
56 | open (my $fh, ">", $mock_encoded_profile);
57 | print $fh $encoded;
58 | close ($fh);
59 | }
60 | else {
61 | open (my $fh, "<", $mock_encoded_profile);
62 | $encoded = do {local $/ = undef; <$fh>};
63 | close ($fh);
64 | }
65 | my %driver_args = %selenium_args;
66 | $driver_args{extra_capabilities} = { firefox_profile => $encoded };
67 | my $driver = Selenium::Remote::Driver->new(%driver_args);
68 |
69 | ok(defined $driver, "made a driver without dying");
70 |
71 | # We don't have to `$driver->get` because our extension should do
72 | # it for us. However, the initial automatic homepage load found in
73 | # the preference 'browser.startup.homepage' isn't blocking, so we
74 | # need to wait until the page is loaded (when we can find
75 | # elements)
76 | $driver->find_element("h1", "tag_name");
77 | cmp_ok($driver->get_current_url, '=~', qr/$domain/i,
78 | "profile loaded and preference respected!");
79 |
80 | $driver->get($website . '/index.html');
81 | cmp_ok($driver->get_text("body", "tag_name"), "=~", qr/ruleset/,
82 | "custom profile with extension loaded");
83 | }
84 |
85 | PREFERENCES: {
86 | my $profile = Selenium::Firefox::Profile->new();
87 | # We're keeping the expected values together as we accumulate them
88 | # so we can validate them all at the end in the pack_and_unpack
89 | # section
90 | my $expected = {};
91 |
92 | STRINGS_AND_INTEGERS: {
93 | my $prefs = {
94 | 'string' => "howdy, there",
95 | 'integer' => 12345,
96 | 'string.like.integer' => '"12345"',
97 | };
98 |
99 | foreach (keys %$prefs) {
100 | my $q = $_ eq 'string' ? '"' : '';
101 | $expected->{$_} = $q . $prefs->{$_} . $q;
102 | }
103 |
104 | $profile->set_preference(%$prefs);
105 |
106 | foreach (keys %$prefs) {
107 | cmp_ok($profile->get_preference($_), "eq", $expected->{$_},
108 | "$_ preference is formatted properly");
109 | }
110 | }
111 |
112 | BOOLEANS: {
113 | my $prefs = {
114 | 'boolean.true' => 1,
115 | 'boolean.false' => 0,
116 | };
117 |
118 | foreach (keys %$prefs) {
119 | $expected->{$_} = $prefs->{$_} ? 'true' : 'false';
120 | }
121 |
122 | $profile->set_boolean_preference(%$prefs);
123 |
124 | foreach (keys %$prefs) {
125 | cmp_ok($profile->get_preference($_), "eq", $expected->{$_},
126 | "$_ pref is formatted correctly");
127 | }
128 |
129 | $profile->set_preference(
130 | 'boolean.true.2' => JSON::true,
131 | 'boolean.false.2' => JSON::false
132 | );
133 | is($profile->get_preference('boolean.true.2'), 'true',
134 | 'format true booleans via set_preference & JSON::true');
135 | is($profile->get_preference('boolean.false.2'), 'false',
136 | 'format false booleans via set_preference & JSON::false');
137 | }
138 |
139 | PACK_AND_UNPACK: {
140 | my $encoded = $profile->_encode();
141 | my $fh = File::Temp->new();
142 | print $fh decode_base64($encoded);
143 | close $fh;
144 |
145 | my $userjs;
146 | unzip $fh->filename => \$userjs, Name => "user.js"
147 | or die "unzip failed: $UnzipError\n";
148 |
149 | foreach (keys %$expected) {
150 | my $value = $expected->{$_};
151 | cmp_ok($userjs, "=~", qr/$value\);/,
152 | "$_ preference is formatted properly after packing and unpacking");
153 | }
154 | }
155 |
156 | MUTABLE_WEBDRIVER: {
157 | my $prefs = {
158 | 'browser.startup.homepage' => 'mutable!'
159 | };
160 |
161 | my $profile = Selenium::Firefox::Profile->new;
162 | $profile->set_preference(%$prefs);
163 | $profile->add_webdriver('port');
164 |
165 | my $homepage_pref = $profile->get_preference('browser.startup.homepage');
166 | is($homepage_pref, '"mutable!"', 'can mutate webdriver.json preferences');
167 | }
168 | }
169 |
170 | CROAKING: {
171 | my $profile = Selenium::Firefox::Profile->new;
172 |
173 | eval { $profile->add_extension('gibberish'); };
174 | cmp_ok($@, '=~', qr/File not found/i, 'throws on missing file');
175 |
176 | eval { $profile->add_extension($FindBin::Bin . '/00-load.t'); };
177 | cmp_ok($@, '=~', qr/xpi format/i, "caught invalid extension filetype");
178 |
179 | eval {
180 | $profile->add_extension($fixture_dir . 'invalid-extension.xpi') ;
181 | $profile->_encode;
182 | };
183 | ok($@ =~ /install\.rdf/i, "caught invalid extension structure");
184 |
185 | eval {
186 | my %driver_args = %selenium_args;
187 | $driver_args{firefox_profile} = 'clearly invalid';
188 | my $croakingDriver = Selenium::Remote::Driver->new(
189 | %driver_args
190 | );
191 | };
192 | ok ($@ =~ /coercion.*failed/, "caught invalid extension in driver constructor");
193 | }
194 |
195 | PROFILE_DIR: {
196 | my $tempdir = File::Temp->newdir;
197 | my $dirname = $tempdir->dirname;
198 |
199 | my $profile = Selenium::Firefox::Profile->new( profile_dir => $dirname );
200 | ok( $profile->{profile_dir} eq $dirname, "profile_dir passed to constructor" );
201 |
202 | $profile->_layout_on_disk;
203 | ok( -f $dirname . '/user.js', "wrote to profile_dir" );
204 | }
205 |
206 | done_testing;
207 |
--------------------------------------------------------------------------------
/t/Remote-Connection.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Test::More;
4 | use Test::Fatal;
5 | use Test::LWP::UserAgent;
6 |
7 | BEGIN: {
8 | unless (use_ok('Selenium::Remote::RemoteConnection')) {
9 | BAIL_OUT("Couldn't load Selenium::Remote::RemoteConnection");
10 | exit;
11 | }
12 | }
13 |
14 | ABSOLUTE_PATH_REDIRECTS: {
15 | my $tua = Test::LWP::UserAgent->new(max_redirect => 0);
16 |
17 | $tua->map_response(qr/redirect/, HTTP::Response->new(303, undef, ['Location' => '/elsewhere']));
18 | $tua->map_response(qr/^http:\/\/localhost:80\/elsewhere$/, HTTP::Response->new(
19 | 200,
20 | 'OK',
21 | ['Content-Type' => 'application/json'],
22 | ''
23 | ));
24 |
25 | my $conn = Selenium::Remote::RemoteConnection->new(
26 | remote_server_addr => 'localhost',
27 | port => '80',
28 | ua => $tua
29 | );
30 |
31 | my $redirect_endpoint = {
32 | method => 'GET',
33 | url => 'http://localhost/redirect'
34 | };
35 |
36 | my $resp = $conn->request($redirect_endpoint);
37 | is($resp->{cmd_status}, 'OK', 'can redirect to absolute path');
38 | }
39 |
40 | REDIRECT: {
41 | my $tua = Test::LWP::UserAgent->new(
42 | max_redirect => 0
43 | );
44 |
45 | $tua->map_response(qr/redirect/, HTTP::Response->new(303, undef, ['Location' => 'http://localhost/elsewhere']));
46 | $tua->map_response(qr/elsewhere/, HTTP::Response->new(200, 'OK', undef, ''));
47 |
48 | my $conn = Selenium::Remote::RemoteConnection->new(
49 | remote_server_addr => 'localhost',
50 | port => '',
51 | ua => $tua
52 | );
53 |
54 | my $redirect_endpoint = {
55 | method => 'GET',
56 | url => 'http://localhost/redirect'
57 | };
58 |
59 | is( exception { $conn->request($redirect_endpoint) }, undef,
60 | '303 redirects no longer kill us');
61 | }
62 |
63 | WD_CONTEXT: {
64 | my $tua = Test::LWP::UserAgent->new;
65 | my $hit_custom_context = 0;
66 | my $response = sub {
67 | is($_[0]->uri, 'http://addr:port/test/anything', 'can construct url with custom wd_context' );
68 | $hit_custom_context++;
69 | return HTTP::Response->new(200, 'OK', undef, '')
70 | };
71 | $tua->map_response(qr/test/, $response);
72 |
73 | my $conn = Selenium::Remote::RemoteConnection->new(
74 | remote_server_addr => 'addr',
75 | port => 'port',
76 | wd_context_prefix => '/test',
77 | ua => $tua
78 | );
79 |
80 | my $endpoint = { method => 'GET', url => 'anything' };
81 |
82 | $conn->request($endpoint);
83 | ok($hit_custom_context, 'wd_context is set up properly');
84 | }
85 |
86 |
87 | done_testing;
88 |
--------------------------------------------------------------------------------
/t/Test-Selenium-Remote-Driver-google.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | use Test::More;
5 | use Test::MockModule;
6 |
7 | use Test::Selenium::Remote::Driver;
8 | use Selenium::Remote::Mock::RemoteConnection;
9 |
10 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
11 |
12 | use FindBin;
13 | use lib $FindBin::Bin . '/lib';
14 | use TestHarness;
15 |
16 | my $harness = TestHarness->new(
17 | this_file => $FindBin::Script
18 | );
19 | my %selenium_args = %{ $harness->base_caps };
20 |
21 | my $selfmock = Test::MockModule->new('Selenium::Remote::Driver');
22 | $selfmock->mock('new_session', sub { my $self = shift; $self->{session_id} = "58aff7be-e46c-42c0-ae5e-571ea1c1f466" });
23 |
24 | # Try to find
25 | my $t = Test::Selenium::Remote::Driver->new(
26 | %selenium_args
27 | );
28 | $t->get_ok('http://www.google.com');
29 | $t->title_like(qr/Google/, 'head retrieved');
30 | $t->body_like(qr/Google/, 'body retrieved');
31 |
32 | done_testing();
33 |
--------------------------------------------------------------------------------
/t/Test-Selenium-Remote-WebElement.t:
--------------------------------------------------------------------------------
1 | use Test::More;
2 | use Selenium::Remote::Mock::Commands;
3 | use Selenium::Remote::Mock::RemoteConnection;
4 | use Test::Selenium::Remote::Driver;
5 | use Test::Selenium::Remote::WebElement;
6 |
7 | # Start off by faking a bunch of Selenium::Remote::WebElement calls succeeding
8 | my $mock_commands = Selenium::Remote::Mock::Commands->new;
9 | my $spec = { };
10 |
11 | foreach my $k (
12 | qw/clearElement clickElement submitElement sendKeysToElement isElementSelected isElementEnabled isElementDisplayed/
13 | ) {
14 | $spec->{$k} = sub { return { status => 'OK', return => 1 }};
15 | }
16 |
17 | $spec->{getElementTagName} = sub { return { status => 'OK', return => 'iframe' }};
18 | $spec->{getElementValue} = sub { return { status => 'OK', return => 'my_value' }};
19 | $spec->{getElementText} = sub { return { status => 'OK', return => "my_text\nis fantastic" }};
20 | $spec->{getElementAttribute} = sub { my @args = @_; my $name = $args[0]->{name}; return { status => 'OK', return => "my_$name" }};
21 |
22 | my $driver = Test::Selenium::Remote::Driver->new(
23 | remote_conn => Selenium::Remote::Mock::RemoteConnection->new( spec => $spec, mock_cmds => $mock_commands ),
24 | commands => $mock_commands,
25 | );
26 |
27 |
28 | my $successful_element = Test::Selenium::Remote::WebElement->new(
29 | id => 'placeholder_id',
30 | driver => $driver
31 | );
32 | $successful_element->clear_ok;
33 | $successful_element->click_ok;
34 | $successful_element->submit_ok;
35 | $successful_element->is_selected_ok;
36 | $successful_element->is_enabled_ok;
37 | $successful_element->is_displayed_ok;
38 | $successful_element->send_keys_ok('Hello World');
39 | $successful_element->tag_name_is( 'iframe', 'we got an iframe tag' );
40 | $successful_element->tag_name_isnt( 'BOOM', 'tag name is not boom' );
41 | $successful_element->tag_name_unlike( qr/BOOM/, "tag_name doesn't match BOOM" );
42 | $successful_element->value_is( 'my_value', 'Got an my_value value?' );
43 | $successful_element->value_isnt( 'BOOM', 'Not BOOM.' );
44 | $successful_element->value_like( qr/val/, 'Matches my_value value?' );
45 | $successful_element->value_unlike( qr/BOOM/, "value doesn't match BOOM" );
46 | $successful_element->text_is( "my_text\nis fantastic", 'Got an my_text value?' );
47 | $successful_element->text_isnt( 'BOOM', 'Not BOOM.' );
48 | $successful_element->text_like( qr/tex/, 'Matches my_text value?' );
49 | $successful_element->text_unlike( qr/BOOM/, "text doesn't match BOOM" );
50 | $successful_element->attribute_is( 'foo', 'my_foo', 'attribute_is matched' );
51 | $successful_element->attribute_isnt( 'foo', 'not_foo', 'attribute_is not_foo' );
52 | $successful_element->attribute_like( 'foo',qr/foo/, 'Matches my_attribute' );
53 | $successful_element->attribute_unlike( 'bar',qr/foo/, "Attribute does not match foo" );
54 |
55 |
56 |
57 | # css_attribute_is($attr_name,$match_str,$test_name);
58 | # css_attribute_isnt($attr_name,$match_str,$test_name);
59 | # css_attribute_like($attr_name,$match_re,$test_name);
60 | # css_attribute_unlike($attr_name,$match_re,$test_name);
61 |
62 | done_testing();
63 |
--------------------------------------------------------------------------------
/t/bin/docker-record-linux:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | REPO=$(git rev-parse --show-toplevel)
4 | cd $REPO
5 |
6 | docker pull gempesaw/docker-selenium-remote-driver
7 | docker run --security-opt=seccomp=unconfined \
8 | -v $REPO:/opt/Selenium-Remote-Driver \
9 | -it --rm gempesaw/docker-selenium-remote-driver $1
10 |
--------------------------------------------------------------------------------
/t/bin/record.pl:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env perl
2 | use strict;
3 | use warnings;
4 |
5 | use Cwd qw/abs_path/;
6 | use FindBin;
7 | # We can only dzil from the root of the repository.
8 | my $this_folder = $FindBin::Bin . '/../../'; # t/bin/../../
9 | my $repo_root = abs_path($this_folder) . '/';
10 |
11 | reset_env();
12 | start_server();
13 |
14 | my $built_lib = find_built_lib();
15 | my $export = $^O eq 'MSWin32' ? 'set' : 'export';
16 | my $wait = $^O eq 'MSWin32' ? 'START /WAIT' : '';
17 | my $prove_opts = '-I' . $built_lib .' -j9 -r --verbose --trap --merge --state=save,slow';
18 | my $default_prove = "prove $prove_opts t/";
19 | my $executable = $ARGV[0] ? "perl -I$built_lib $ARGV[0]" : $default_prove;
20 | my $command = "$export WD_MOCKING_RECORD=1 && cd $repo_root && $executable";
21 | print "Executing: $command\n";
22 | print `$command`;
23 | reset_env();
24 |
25 | sub find_built_lib {
26 | my $built_lib = glob($repo_root . 'Selenium-Remote-Driver-*/lib');
27 | if (not defined $built_lib) {
28 | print 'Building a dist.' . "\n";
29 | print `cd $repo_root && dzil build`;
30 | }
31 | # If built_lib wasn't around in the first place, we'll have to glob
32 | # for it again.
33 | $built_lib ||= glob($repo_root . 'Selenium-Remote-Driver-*/lib');
34 | return $built_lib;
35 | }
36 |
37 | sub start_server {
38 | if ($^O eq 'MSWin32') {
39 | system('start "TEMP_HTTP_SERVER" /MIN perl ' . $repo_root . 't/http-server.pl');
40 | }
41 | else {
42 | system('perl ' . $repo_root . 't/http-server.pl > /dev/null &');
43 | }
44 | print 'Starting a new server.' . "\n";
45 | }
46 |
47 | sub kill_server {
48 | if ($^O eq 'MSWin32') {
49 | system("taskkill /FI \"WINDOWTITLE eq TEMP_HTTP_SERVER\"");
50 | }
51 | else {
52 | `ps aux | grep [h]ttp-server\.pl | awk '{print \$2}' | xargs kill`;
53 | }
54 | }
55 |
56 |
57 | sub reset_env {
58 | print 'dzil cleaning...';
59 | `cd $repo_root && dzil clean`;
60 | print 'and taking out any existing servers. ' . "\n";
61 | kill_server();
62 | }
63 |
--------------------------------------------------------------------------------
/t/convenience.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Selenium::Chrome;
4 | use Selenium::Firefox;
5 | use Selenium::InternetExplorer;
6 | use Selenium::PhantomJS;
7 | use Test::Selenium::Chrome;
8 | use Test::Selenium::Firefox;
9 | use Test::Selenium::InternetExplorer;
10 | use Test::Selenium::PhantomJS;
11 | use Test::More;
12 |
13 | $Selenium::Remote::Driver::FORCE_WD2 = 1;
14 |
15 | use FindBin;
16 | use lib $FindBin::Bin . '/lib';
17 | use TestHarness;
18 |
19 | my $harness = TestHarness->new(
20 | this_file => $FindBin::Script
21 | );
22 |
23 | my %caps = %{ $harness->base_caps };
24 | $caps{remote_server_addr} = '127.0.0.1';
25 | delete $caps{browser_name};
26 |
27 | subtest Driver => sub {
28 | my $phantomjs = Selenium::PhantomJS->new( %caps );
29 | ok( $phantomjs->browser_name eq 'phantomjs', 'convenience phantomjs is okay' );
30 | $phantomjs->quit;
31 |
32 | my $firefox = Selenium::Firefox->new( %caps );
33 | ok( $firefox->browser_name eq 'firefox', 'convenience firefox is okay' );
34 | $firefox->quit;
35 |
36 | SKIP : {
37 | skip("Don't have time to fix this failing test, test in at/ passes",1);
38 | my $chrome = Selenium::Chrome->new( %caps );
39 | #This actually works fine, don't have time to fix this test
40 | ok( $chrome->browser_name eq 'chrome', 'convenience chrome is okay' );
41 | $chrome->quit;
42 | };
43 | };
44 |
45 | subtest TestDriver => sub {
46 | my $phantomjs = Test::Selenium::PhantomJS->new( %caps );
47 | ok( $phantomjs->browser_name eq 'phantomjs', 'convenience phantomjs is okay' );
48 | $phantomjs->get_ok('about:config');
49 | $phantomjs->quit;
50 |
51 | my $firefox = Test::Selenium::Firefox->new( %caps );
52 | $firefox->get_ok('about:config');
53 | ok( $firefox->browser_name eq 'firefox', 'convenience firefox is okay' );
54 | $firefox->quit;
55 |
56 | SKIP : {
57 | skip("Don't have time to fix this failing test, test in at/ passes",1);
58 |
59 | my $chrome = Test::Selenium::Chrome->new( %caps );
60 | ok( $chrome->browser_name eq 'chrome', 'convenience chrome is okay' );
61 | $chrome->get_ok('about:config');
62 | $chrome->quit;
63 | }
64 | };
65 |
66 |
67 | done_testing;
68 |
--------------------------------------------------------------------------------
/t/error.t:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use Test::More;
4 | use Test::Fatal;
5 | use Test::LWP::UserAgent;
6 | use IO::Socket::INET;
7 |
8 | BEGIN: {
9 | unless (use_ok('Selenium::Remote::Driver')) {
10 | BAIL_OUT("Couldn't load Selenium::Remote::Driver");
11 | exit;
12 | }
13 | }
14 |
15 | UNAVAILABLE_BROWSER: {
16 | my $tua = Test::LWP::UserAgent->new;
17 |
18 | $tua->map_response(qr{status}, HTTP::Response->new(200, 'OK'));
19 | $tua->map_response(qr{session}, HTTP::Response->new(
20 | 500,
21 | 'Internal Server Error',
22 | ['Content-Type' => 'application/json'],
23 | '{"status":13,"sessionId":null,"value":{"message":"The path to..."} }'
24 | ));
25 |
26 | like( exception {
27 | Selenium::Remote::Driver->new_from_caps(
28 | ua => $tua,
29 | desired_capabilities => {
30 | browserName => 'chrome'
31 | }
32 | );
33 | }, qr/Could not create new session.*path to/,
34 | 'Errors in browser configuration are passed to user' );
35 | }
36 |
37 | LOCAL: {
38 | like( exception {
39 | Selenium::Remote::Driver->new_from_caps(
40 | port => 80
41 | );
42 | }, qr/Selenium server did not return proper status/,
43 | 'Error message for not finding a selenium server is helpful' );
44 | }
45 |
46 | done_testing;
47 |
--------------------------------------------------------------------------------
/t/http-server.pl:
--------------------------------------------------------------------------------
1 | # This is by no means any where close to a real web server but a rather quick
2 | # solution for testing purposes.
3 |
4 | use warnings;
5 | use strict;
6 | use IO::Socket;
7 | use FindBin;
8 | use File::stat;
9 | use File::Basename;
10 |
11 | my $server = IO::Socket::INET->new(
12 | Proto => 'tcp',
13 | Listen => SOMAXCONN,
14 | LocalPort => 63636,
15 | ReuseAddr => 1
16 | );
17 |
18 | my $server_root = $FindBin::Bin . '/';
19 |
20 | die "Server failed.\n" unless $server;
21 |
22 | while ( my $client = $server->accept() ) {
23 | $client->autoflush(1);
24 |
25 | my $request = <$client>;
26 |
27 | my $filename;
28 | my $filesize;
29 | my $content_type;
30 | my $success = 1;
31 |
32 | if ( $request =~ m|^GET /(.+) HTTP/1.| || $request =~ m|^GET / HTTP/1.| ) {
33 | if ( $1 && -e $server_root . 'www/' . $1 ) {
34 | $filename = $server_root . 'www/' . $1;
35 | }
36 | else {
37 | $success = 0;
38 | $filename = $server_root . 'www/404.html';
39 | }
40 |
41 | my ( undef, undef, $ftype ) = fileparse( $filename, qr/\.[^.]*/ );
42 |
43 | $filesize = stat($filename)->size;
44 | $content_type = "text/html";
45 |
46 | if ($success) {
47 | print $client
48 | "HTTP/1.1 200 OK\nContent-Type: $content_type; charset=utf-8\nContent-Length: $filesize\nServer: \n\n";
49 | }
50 | else {
51 | print $client
52 | "HTTP/1.1 404 Not Found\nContent-Type: $content_type; charset=utf-8\nContent-Length: $filesize\nServer: Perl Test Server\n\n";
53 | }
54 |
55 | open( my $f, "<$filename" );
56 | while (<$f>) { print $client $_ }
57 | }
58 | else {
59 | print $client 'HTTP/1.1 400 Bad Request\n';
60 | }
61 |
62 | close $client;
63 | }
64 |
--------------------------------------------------------------------------------
/t/lib/TestHarness.pm:
--------------------------------------------------------------------------------
1 | package TestHarness;
2 |
3 | # ABSTRACT: Take care of set up for recording/replaying mocks
4 | use FindBin;
5 | use Moo;
6 | use Selenium::Remote::Mock::RemoteConnection;
7 | use Test::More;
8 |
9 | =head1 SYNOPSIS
10 |
11 | my $harness = TestHarness->new(
12 | this_file => $FindBin::Script
13 | );
14 | my %selenium_args = %{ $harness->base_caps };
15 |
16 | =head1 DESCRIPTION
17 |
18 | A setup class for all the repetitive things we need to do before
19 | running tests. First, we're deciding whether the test is in C
20 | or C mode. If we're recording, we'll end up writing all the
21 | HTTP request/response pairs out to L. If we're replaying,
22 | we'll look for our OS-appropriate mock_file and try to read from it.
23 |
24 | After we figure that out, we can instantiate our
25 | Mock::RemoteConnection with the proper constructor arguments and
26 | return that as our base_args for use in the tests! Finally, on
27 | destruction, if we're recording, we make sure to dump out all of the
28 | request/response pairs to the mock_file.
29 |
30 | =attr this_file
31 |
32 | Required. Pass in the short name of the test file in use so we can
33 | figure out where the corresponding recording belongs. For a test file
34 | named C, we'd expect this argument to be
35 | C<01-driver.t>.
36 |
37 | =cut
38 |
39 | has calling_file => (
40 | is => 'ro',
41 | init_arg => 'this_file',
42 | required => 1
43 | );
44 |
45 | =attr record
46 |
47 | Optional. Determines whether or not this test run should record new
48 | mocks, or look up a previous recording to replay against them. If the
49 | parameter is not used during construction, the default behavior is to
50 | check for the environment variable WD_MOCKING_RECORD to be defined and
51 | equal to 1.
52 |
53 | =cut
54 |
55 | has record => (
56 | is => 'ro',
57 | init_args => undef,
58 | default => sub {
59 | if ( defined $ENV{WD_MOCKING_RECORD}
60 | && $ENV{WD_MOCKING_RECORD} == 1 )
61 | {
62 | return 1;
63 | }
64 | else {
65 | return 0;
66 | }
67 | }
68 | );
69 |
70 | has base_caps => (
71 | is => 'rw',
72 | lazy => 1,
73 | default => sub {
74 | my ($self) = @_;
75 | my $args = {
76 | browser_name => 'firefox',
77 | remote_conn => $self->mock_remote_conn
78 | };
79 |
80 | return $args;
81 | }
82 | );
83 |
84 | has mock_remote_conn => (
85 | is => 'ro',
86 | lazy => 1,
87 | builder => sub {
88 | my ($self) = @_;
89 | if ( $self->record ) {
90 | return Selenium::Remote::Mock::RemoteConnection->new( record => 1 );
91 | }
92 | else {
93 | return Selenium::Remote::Mock::RemoteConnection->new(
94 | replay => 1,
95 | replay_file => $self->mock_file
96 | );
97 | }
98 | }
99 | );
100 |
101 | has mock_file => (
102 | is => 'ro',
103 | lazy => 1,
104 | builder => sub {
105 | my ($self) = @_;
106 |
107 | # Since FindBin uses a Begin block, and we're using it in the
108 | # tests themselves, $FindBin::Bin will already be initialized
109 | # to the folder that the *.t files live in - that is, `t`.
110 | my $mock_folder = $FindBin::Bin . '/mock-recordings/';
111 |
112 | my $test_name = lc( $self->calling_file );
113 | $test_name =~ s/\.t$//;
114 |
115 | my $mock_file = $mock_folder . $test_name . '-mock.json';
116 |
117 | # If we're replaying, we need a mock to read from. Otherwise,
118 | # we can't do anything
119 | if ( not $self->record ) {
120 | plan skip_all =>
121 | "Mocking of tests is not been enabled for this platform"
122 | unless -e $mock_file;
123 | }
124 |
125 | return $mock_file;
126 | }
127 | );
128 |
129 | has website => (
130 | is => 'ro',
131 | default => sub {
132 | my ($self) = @_;
133 | my $port = 63636;
134 |
135 | return 'http://' . $self->domain . ':' . $port;
136 | }
137 | );
138 |
139 | has domain => (
140 | is => 'ro',
141 | default => sub { 'localhost' }
142 | );
143 |
144 | sub DEMOLISH {
145 | my ($self) = @_;
146 | if ( $self->record ) {
147 | $self->mock_remote_conn->dump_session_store( $self->mock_file );
148 | }
149 | }
150 |
151 | 1;
152 |
--------------------------------------------------------------------------------
/t/mock-recordings/test-selenium-remote-driver-google-mock.json:
--------------------------------------------------------------------------------
1 | {
2 | "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":false,\"platform\":\"ANY\",\"version\":null}}" : [
3 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:39 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 683\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:46 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":null,\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":1820151494,\"value\":{\"moz:profile\":\"/tmp/rust_mozprofile.2zXf9zXkIHq8\",\"rotatable\":false,\"timeouts\":{\"implicit\":0.0,\"page load\":300000.0,\"script\":30000.0},\"pageLoadStrategy\":\"normal\",\"platform\":\"ANY\",\"specificationLevel\":0.0,\"moz:accessibilityChecks\":false,\"webdriver.remote.sessionid\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"acceptInsecureCerts\":false,\"browserVersion\":\"52.0.2\",\"platformVersion\":\"16.5.0\",\"moz:processID\":42129.0,\"browserName\":\"firefox\",\"takesScreenshot\":true,\"javascriptEnabled\":true,\"platformName\":\"darwin\",\"cssSelectorsEnabled\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
4 | ],
5 | "POST session/58aff7be-e46c-42c0-ae5e-571ea1c1f466/element {\"using\":\"xpath\",\"value\":\"//body\"}" : [
6 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:49 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 169\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:49 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":913793544,\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
7 | ],
8 | "POST session/58aff7be-e46c-42c0-ae5e-571ea1c1f466/url {\"url\":\"http://www.google.com\"}" : [
9 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:46 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 158\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:49 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":901032388,\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
10 | ],
11 | "GET session/58aff7be-e46c-42c0-ae5e-571ea1c1f466/title {}" : [
12 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:49 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 162\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:49 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":676877692,\"value\":\"Google\",\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
13 | ],
14 | "GET session/58aff7be-e46c-42c0-ae5e-571ea1c1f466/element/0/text {}" : [
15 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:49 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 457\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:49 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":699248581,\"value\":\"Screen reader users, click here to turn off Google Instant.\\nGmail\\nImages\\nSign in\\nÃ\\nGet to Google faster. Switch your default search engine to Google.\\nYes, show me\\nExplore the Syrian refugee crisis, in collaboration with the UN Refugee Agency\\nPrivacy Terms Settings\\nAdvertising Business About\",\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
16 | ],
17 | "DELETE session/58aff7be-e46c-42c0-ae5e-571ea1c1f466 {}" : [
18 | "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Thu, 25 May 2017 00:45:49 GMT\nServer: Jetty(9.4.3.v20170317)\nContent-Length: 158\nContent-Type: application/json;charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Thu, 25 May 2017 00:45:50 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"58aff7be-e46c-42c0-ae5e-571ea1c1f466\",\"hCode\":550155213,\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/t/uploadTest:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teodesian/Selenium-Remote-Driver/1f078d8f41ae02cbeb51d0a6ad460c0aee6c98fc/t/uploadTest
--------------------------------------------------------------------------------
/t/www/404.html:
--------------------------------------------------------------------------------
1 | 404 - Not Found
2 |
--------------------------------------------------------------------------------
/t/www/alerts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Testing Alerts
4 |
5 |
6 |
7 | Testing Alerts and Stuff
8 |
9 |
10 |
11 | This tests alerts: click me
12 |
13 |
14 | This is a test of a prompt: test prompt
15 |
16 | This is a test of a confirm: test confirm
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/t/www/cookies.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Testing cookies
4 |
5 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/t/www/dragAndDropTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
58 |
59 |
60 |
61 |
62 |
63 | "Hi there
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/t/www/formPage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | We Leave From Here
4 |
5 |
11 |
12 |
13 | Home Page Link
14 |
15 | There should be a form here:
16 |
17 |
20 |
21 |
24 |
25 |
57 |
58 |
67 |
68 |
69 |
70 | One
71 | Two
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/t/www/frameset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/t/www/icon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teodesian/Selenium-Remote-Driver/1f078d8f41ae02cbeb51d0a6ad460c0aee6c98fc/t/www/icon.gif
--------------------------------------------------------------------------------
/t/www/iframes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This page has iframes
4 |
5 |
6 | This is the heading
7 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/t/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello WebDriver
4 |
5 |
6 | Heading
7 |
8 | A single line of text
9 |
10 |
11 |
A div containing
12 | More than one line of text
13 |
14 |
and block level elements
15 |
16 |
17 | An inline element
18 |
19 | This line has lots
20 |
21 | of spaces.
22 |
23 |
24 | This line has a non-breaking space
25 |
26 | This line has a non-breaking space and spaces
27 |
28 | This line has text within elements that are meant to be displayed
29 | inline
30 |
31 | This section has a
preformatted
32 | text block
33 | within in
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 | foo bar
54 |
55 |
56 |
57 |
62 |
63 | Top level
64 |
67 |
68 |
69 | I am displayed.
70 | I am hidden.
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/t/www/invalid-extension.xpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teodesian/Selenium-Remote-Driver/1f078d8f41ae02cbeb51d0a6ad460c0aee6c98fc/t/www/invalid-extension.xpi
--------------------------------------------------------------------------------
/t/www/javascriptPage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Testing Javascript
5 |
34 |
54 |
67 |
68 |
69 | Type Stuff
70 |
71 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | Key Up:
88 | Key Down:
89 | Key Press:
90 | Change:
91 |
92 |
93 |
94 |
95 | Foo
96 | Bar
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
112 |
113 | What's for dinner?
114 |
115 |
116 |
Click for the mouse down event
117 |
Here's some text
118 |
119 |
120 |
121 |
Click for the mouse up event
122 |
123 |
124 |
125 |
Click for the mouse click event
126 |
127 |
128 |
129 | Clicking this causes a JS exception in the click handler
130 |
131 |
132 |
171 |
172 | A paragraph suppressed using CSS display=none
173 |
174 |
175 |
Displayed
176 |
177 |
178 |
179 |
Display set to none
180 |
181 |
Hidden
182 |
183 |
188 |
189 |
190 |
191 | Check box you can't see
192 |
193 |
194 |
195 |
A sub-element that is explicitly visible using CSS visibility=visible
196 |
197 |
198 |
199 |
200 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
This should be greenish
211 |
212 | So should this
213 | But this is red
214 |
215 |
216 |
217 | This is a foo
218 |
219 | This is a bar
220 |
221 | Close window
222 |
223 |
224 |
I should be deleted when you click my containing div
225 |
Whereas, I should not
226 |
227 |
228 |
229 | Click to hide me.
230 |
231 |
232 |
233 | Click actions delayed by 3000ms:
234 |
236 | Click to show black box
237 |
238 |
241 | Click to hide black box
242 |
243 |
244 |
245 |
246 |
247 |
248 |
--------------------------------------------------------------------------------
/t/www/metakeys.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Testing meta keys
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/t/www/nestedElements.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | outside
4 | outside
5 |
8 |
9 | Here's a checkbox:
10 |
11 | One
12 | Two
13 | Four
14 | Still learning how to count, apparently
15 |
16 |
17 |
18 |
19 | Here's a checkbox:
20 |
21 | One
22 | Two
23 | Four
24 | Still learning how to count, apparently
25 |
26 |
27 | One
28 | Two
29 | Four
30 | Still learning how to count, apparently
31 |
32 |
33 |
34 | hello world
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Here's a checkbox:
46 |
47 | One
48 | Two
49 | Four
50 | Still learning how to count, apparently
51 |
52 |
53 |
54 |
55 | Here's a checkbox:
56 |
57 | One
58 | Two
59 | Four
60 | Still learning how to count, apparently
61 |
62 |
63 | One
64 | Two
65 | Four
66 | Still learning how to count, apparently
67 |
68 |
69 |
70 | hello world
71 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Here's a checkbox:
82 |
83 | One
84 | Two
85 | Four
86 | Still learning how to count, apparently
87 |
88 |
89 |
90 |
91 | Here's a checkbox:
92 |
93 | One
94 | Two
95 | Four
96 | Still learning how to count, apparently
97 |
98 |
99 | One
100 | Two
101 | Four
102 | Still learning how to count, apparently
103 |
104 |
105 |
106 | hello world
107 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | Here's a checkbox:
118 |
119 | One
120 | Two
121 | Four
122 | Still learning how to count, apparently
123 |
124 |
125 |
126 |
127 | Here's a checkbox:
128 |
129 | One
130 | Two
131 | Four
132 | Still learning how to count, apparently
133 |
134 |
135 | One
136 | Two
137 | Four
138 | Still learning how to count, apparently
139 |
140 |
141 |
142 | hello world
143 |
147 |
148 | Span with class of one
149 |
150 | Find me
151 | Also me
152 | But not me
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/t/www/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | JavaScript Popup Example 3
4 |
5 |
11 |
12 | JavaScript Popup
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/t/www/redisplay.xpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teodesian/Selenium-Remote-Driver/1f078d8f41ae02cbeb51d0a6ad460c0aee6c98fc/t/www/redisplay.xpi
--------------------------------------------------------------------------------
/t/www/xhtmlTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | XHTML Test Page
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
If you'd like to go elsewhere then click me .
21 |
22 |
Alternatively, this goes to the same place .
23 |
24 |
25 |
26 |
27 |
28 | This link has the same text as another link:
click me .
29 |
30 |
31 |
35 |
36 |
41 |
42 |
45 |
46 |
47 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Spaced out
63 |
64 |
65 |
--------------------------------------------------------------------------------
/tidyall.ini:
--------------------------------------------------------------------------------
1 | [PerlTidy]
2 | select = {lib,bin}/**/*
3 | argv = -noll -it=2
4 |
--------------------------------------------------------------------------------
/weaver.ini:
--------------------------------------------------------------------------------
1 | [@CorePrep]
2 |
3 | [-SingleEncoding]
4 |
5 | [Name]
6 | [Version]
7 |
8 | [Region / prelude]
9 |
10 | [Generic / SYNOPSIS]
11 | [Generic / DESCRIPTION]
12 | [Generic / OVERVIEW]
13 |
14 | [Collect / ATTRIBUTES]
15 | command = attr
16 |
17 | [Collect / METHODS]
18 | command = method
19 |
20 | [Collect / FUNCTIONS]
21 | command = func
22 |
23 | [Leftovers]
24 |
25 | [Region / postlude]
26 |
27 | ; [Authors]
28 | [GenerateSection / AUTHORS ]
29 | text = Current Maintainers:
30 | text =
31 | text = =over 4
32 | text =
33 | text = =item *
34 | text =
35 | text = George S. Baugh
36 | text =
37 | text = =back
38 | text =
39 | text = Previous maintainers:
40 | text =
41 | text = =over 4
42 | text =
43 | text = =item *
44 | text =
45 | text = Daniel Gempesaw
46 | text =
47 | text = =item *
48 | text =
49 | text = Emmanuel Peroumalnaïk
50 | text =
51 | text = =item *
52 | text =
53 | text = Luke Closs
54 | text =
55 | text = =item *
56 | text =
57 | text = Mark Stosberg
58 | text =
59 | text = =back
60 | text =
61 | text = Original authors:
62 | text =
63 | text = =over 4
64 | text =
65 | text = =item *
66 | text =
67 | text = Aditya Ivaturi
68 | text =
69 | text = =back
70 |
71 | [Contributors]
72 | [GenerateSection / COPYRIGHT AND LICENSE]
73 | text = Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
74 | text =
75 | text = Copyright (c) 2014-2017 Daniel Gempesaw
76 | text =
77 | text = Copyright (c) 2018-2021 George S. Baugh
78 | text =
79 | text = Licensed under the Apache License, Version 2.0 (the "License");
80 | text = you may not use this file except in compliance with the License.
81 | text = You may obtain a copy of the License at
82 | text =
83 | text = http://www.apache.org/licenses/LICENSE-2.0
84 | text =
85 | text = Unless required by applicable law or agreed to in writing, software
86 | text = distributed under the License is distributed on an "AS IS" BASIS,
87 | text = WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88 | text = See the License for the specific language governing permissions and
89 | text = limitations under the License.
90 |
91 |
92 | [-Transformer]
93 | transformer = List
94 |
--------------------------------------------------------------------------------