File Coverage

blib/lib/Appium.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Appium;
2             $Appium::VERSION = '0.0803';
3             # ABSTRACT: Perl bindings to the Appium mobile automation framework (WIP)
4 2     2   1531 use Carp qw/croak/;
  2         3  
  2         121  
5 2     2   8 use feature qw/state/;
  2         2  
  2         167  
6 2     2   1061 use Moo;
  2         24982  
  2         12  
7 2     2   3800 use MooX::Aliases 0.001005;
  2         5526  
  2         11  
8              
9 2     2   1716 use Appium::Commands;
  0            
  0            
10             use Appium::Element;
11             use Appium::ErrorHandler;
12             use Appium::SwitchTo;
13             use Appium::TouchActions;
14              
15             use Selenium::Remote::Driver 0.2202;
16             use Selenium::Remote::RemoteConnection;
17             extends 'Selenium::Remote::Driver';
18              
19              
20              
21             use constant FINDERS => {
22             %{ Selenium::Remote::Driver->FINDERS },
23             ios => '-ios uiautomation',
24             ios_uiautomation => '-ios uiautomation',
25             android => '-android uiautomator',
26             android_uiautomator => '-android uiautomator',
27             accessibility_id => 'accessibility id'
28             };
29              
30             has '+desired_capabilities' => (
31             is => 'rw',
32             required => 1,
33             alias => 'caps',
34             );
35              
36             has '_type' => (
37             is => 'rw',
38             lazy => 1,
39             coerce => sub {
40             my $device = shift || 'iOS';
41              
42             croak 'platformName must be Android or iOS'
43             unless grep { $_ eq $device } qw/Android iOS/;
44              
45             return $device;
46             }
47             );
48              
49             has '+remote_server_addr' => (
50             is => 'ro',
51             default => sub { 'localhost' }
52             );
53              
54             has '+port' => (
55             is => 'ro',
56             default => sub { 4723 }
57             );
58              
59             has '+commands' => (
60             is => 'ro',
61             default => sub { Appium::Commands->new }
62             );
63              
64             has '+remote_conn' => (
65             is => 'ro',
66             lazy => 1,
67             builder => sub {
68             my $self = shift;
69             return Selenium::Remote::RemoteConnection->new(
70             remote_server_addr => $self->remote_server_addr,
71             port => $self->port,
72             ua => $self->ua,
73             error_handler => Appium::ErrorHandler->new
74             );
75             }
76             );
77              
78             has 'touch_actions' => (
79             is => 'ro',
80             lazy => 1,
81             init_arg => undef,
82             handles => [ qw/tap/ ],
83             default => sub { Appium::TouchActions->new( driver => shift ); }
84             );
85              
86              
87             has 'webelement_class' => (
88             is => 'rw',
89             default => sub { 'Appium::Element' }
90             );
91              
92             sub BUILD {
93             my ($self) = @_;
94              
95             $self->_type($self->desired_capabilities->{platformName});
96             }
97              
98              
99             sub contexts {
100             my ($self) = @_;
101              
102             my $res = { command => 'contexts' };
103             return $self->_execute_command( $res );
104             }
105              
106              
107             sub current_context {
108             my ($self) = @_;
109              
110             my $res = { command => 'get_current_context' };
111             my $params = {};
112              
113             return $self->_execute_command( $res, $params );
114             }
115              
116              
117             has 'switch_to' => (
118             is => 'lazy',
119             init_arg => undef,
120             default => sub { Appium::SwitchTo->new( driver => shift ); }
121             );
122              
123              
124             sub hide_keyboard {
125             my ($self, %args) = @_;
126              
127             my $res = { command => 'hide_keyboard' };
128             my $params = {};
129              
130             if (exists $args{key_name}) {
131             $params->{keyName} = $args{key_name}
132             }
133             elsif (exists $args{key}) {
134             $params->{key} = $args{key}
135             }
136              
137             # default strategy is tapOutside
138             my $strategy = 'tapOutside';
139             $params->{strategy} = $args{strategy} || $strategy;
140              
141             return $self->_execute_command( $res, $params );
142             }
143              
144              
145             sub app_strings {
146             my ($self, $language) = @_;
147              
148             my $res = { command => 'app_strings' };
149             my $params;
150             if (defined $language ) {
151             $params = { language => $language }
152             }
153             else {
154             $params = {};
155             }
156              
157             return $self->_execute_command( $res, $params );
158             }
159              
160              
161             sub reset {
162             my ($self) = @_;
163              
164             my $res = { command => 'reset' };
165              
166             return $self->_execute_command( $res );
167             }
168              
169              
170             sub press_keycode {
171             my ($self, $keycode, $metastate) = @_;
172              
173             my $res = { command => 'press_keycode' };
174             my $params = {
175             keycode => $keycode,
176             };
177              
178             $params->{metastate} = $metastate if $metastate;
179              
180             return $self->_execute_command( $res, $params );
181             }
182              
183              
184             sub long_press_keycode {
185             my ($self, $keycode, $metastate) = @_;
186              
187             my $res = { command => 'long_press_keycode' };
188             my $params = {
189             keycode => $keycode,
190             };
191              
192             $params->{metastate} = $metastate if $metastate;
193              
194             return $self->_execute_command( $res, $params );
195             }
196              
197              
198             sub current_activity {
199             my ($self) = @_;
200              
201             my $res = { command => 'current_activity' };
202              
203             return $self->_execute_command( $res );
204             }
205              
206              
207             sub pull_file {
208             my ($self, $path) = @_;
209             croak "Please specify a path to pull from the device"
210             unless defined $path;
211              
212             my $res = { command => 'pull_file' };
213             my $params = { path => $path };
214              
215             return $self->_execute_command( $res, $params );
216             }
217              
218              
219             sub pull_folder {
220             my ($self, $path) = @_;
221             croak 'Please specify a folder path to pull'
222             unless defined $path;
223              
224             my $res = { command => 'pull_folder' };
225             my $params = { path => $path };
226              
227             return $self->_execute_command( $res, $params );
228             }
229              
230              
231             sub push_file {
232             my ($self, $path, $data) = @_;
233              
234             my $res = { command => 'push_file' };
235             my $params = {
236             path => $path,
237             data => $data
238             };
239              
240             return $self->_execute_command( $res, $params );
241             }
242              
243              
244             # todo: add better examples of complex find
245              
246             sub complex_find {
247             my ($self, @selector) = @_;
248             croak 'Please specify selection criteria'
249             unless scalar @selector;
250              
251             my $res = { command => 'complex_find' };
252             my $params = { selector => \@selector };
253              
254             return $self->_execute_command( $res, $params );
255             }
256              
257              
258             sub background_app {
259             my ($self, $seconds) = @_;
260              
261             my $res = { command => 'background_app' };
262             my $params = { seconds => $seconds};
263              
264             return $self->_execute_command( $res, $params );
265             }
266              
267              
268             sub is_app_installed {
269             my ($self, $bundle_id) = @_;
270              
271             my $res = { command => 'is_app_installed' };
272             my $params = { bundleId => $bundle_id };
273              
274             return $self->_execute_command( $res, $params );
275             }
276              
277              
278             sub install_app {
279             my ($self, $app_path) = @_;
280              
281             my $res = { command => 'install_app' };
282             my $params = { appPath => $app_path };
283              
284             return $self->_execute_command( $res, $params );
285             }
286              
287              
288             sub remove_app {
289             my ($self, $app_id) = @_;
290              
291             my $res = { command => 'remove_app' };
292             my $params = { appId => $app_id };
293              
294             return $self->_execute_command( $res, $params );
295             }
296              
297              
298             sub launch_app {
299             my ($self) = @_;
300              
301             my $res = { command => 'launch_app' };
302             return $self->_execute_command( $res );
303             }
304              
305              
306             sub close_app {
307             my ($self) = @_;
308              
309             my $res = { command => 'close_app' };
310             return $self->_execute_command( $res );
311             }
312              
313              
314             sub end_test_coverage {
315             my ($self, $intent, $path) = @_;
316              
317             my $res = { command => 'end_test_coverage' };
318             my $params = {
319             intent => $intent,
320             path => $path
321             };
322              
323             return $self->_execute_command( $res, $params );
324             }
325              
326              
327             sub lock {
328             my ($self, $seconds) = @_;
329              
330             my $res = { command => 'lock' };
331             my $params = { seconds => $seconds };
332              
333             return $self->_execute_command( $res, $params );
334             }
335              
336              
337             sub is_locked {
338             my($self) = @_;
339              
340             my $res = { command => 'is_locked' };
341             return $self->_execute_command( $res );
342             }
343              
344              
345             sub shake {
346             my ($self) = @_;
347              
348             my $res = { command => 'shake' };
349             return $self->_execute_command( $res );
350             }
351              
352              
353             sub open_notifications {
354             my ($self) = @_;
355              
356             my $res = { command => 'open_notifications' };
357             return $self->_execute_command( $res );
358             }
359              
360              
361             sub network_connection {
362             my ($self) = @_;
363              
364             my $res = { command => 'network_connection' };
365             return $self->_execute_command( $res );
366             }
367              
368              
369             sub set_network_connection {
370             my ($self, $connection_bitmask) = @_;
371              
372             my $res = { command => 'set_network_connection' };
373             my $params = {
374             parameters => {
375             type => $connection_bitmask
376             }
377             };
378              
379             return $self->_execute_command( $res, $params );
380             }
381              
382              
383              
384             sub page {
385             my ($self) = @_;
386              
387             $self->_get_page;
388             }
389              
390             sub _get_page {
391             my ($self, $element, $level) = @_;
392             return 'TODO: implement page on android' if $self->is_android;
393              
394             $element //= $self->_source_window_with_children;
395             $level //= 0;
396             my $indent = ' ' x $level;
397              
398             # App strings are found in an actual file in the app package
399             # somewhere, so I'm assuming we don't have to worry about them
400             # changing in the middle of our app execution. This may very well
401             # turn out to be a false assumption.
402             state $strings = $self->app_strings;
403              
404             my @details = qw/name label value hint/;
405             if ($element->{visible}) {
406             print $indent . $element->{type} . "\n";
407             foreach (@details) {
408             my $detail = $element->{$_};
409             if ($detail) {
410             print $indent . ' ' . $_ . "\t: " . $detail . "\n" ;
411              
412             foreach my $key (keys %{ $strings }) {
413             my $val = $strings->{$key};
414             if ($val =~ /$detail/) {
415             print $indent . ' id ' . "\t: " . $key . ' => ' . $val . "\n";
416             }
417             }
418             }
419             }
420             }
421              
422             $level++;
423             my @children = @{ $element->{children} };
424             foreach (@children) {
425             $self->_get_page($_, $level);
426             }
427             }
428              
429             sub _source_window_with_children {
430             my ($self, $index) = @_;
431             $index //= 0;
432              
433             my $window = $self->execute_script('UIATarget.localTarget().frontMostApp().windows()[' . $index . '].getTree()');
434             if (scalar @{ $window->{children} }) {
435             return $window;
436             }
437             else {
438             return $self->_source_window_with_children(++$index);
439             }
440             }
441              
442             sub is_android {
443             return shift->_type eq 'Android'
444             }
445              
446             sub is_ios {
447             return shift->_type eq 'iOS'
448             }
449              
450              
451             1;
452              
453             __END__