Procházet zdrojové kódy

Simplify, still more to refactor

George S. Baugh před 5 roky
rodič
revize
646d8e1991

+ 20 - 45
bin/playwright.js

@@ -15,27 +15,6 @@ let spec = JSON.parse(rawdata);
 
 //TODO support device commands
 const argv = yargs
-    .command('firefox', 'Starts a playwright instance of firefox', {
-        firefox: {
-            description: 'Start a firefox instance',
-            alias: 'f',
-            type: 'boolean',
-        }
-    })
-    .command('chrome', 'Starts a playwright instance of chrome', {
-        chrome: {
-            description: 'Start a chrome instance',
-            alias: 'c',
-            type: 'boolean',
-        }
-    })
-    .command('webkit', 'Starts a playwright instance of webkit', {
-        webkit: {
-            description: 'Start a webkit instance',
-            alias: 'w',
-            type: 'boolean',
-        }
-    })
     .option('debug', {
         alias: 'd',
         description: 'Print additional debugging messages',
@@ -46,11 +25,6 @@ const argv = yargs
         description: 'Run on specified port',
         type: 'number',
     })
-    .option('visible', {
-        alias: 'v',
-        description: 'Run with headless mode off',
-        type: 'boolean',
-    })
     .help()
     .alias('help', 'h')
     .argv;
@@ -59,34 +33,35 @@ const app = express();
 const port = argv.port || 6969;
 
 var objects = {};
+var browsers = { 'firefox' : firefox, 'chrome' : chromium, 'webkit' : webkit };
 
 app.use(express.json())
 
-app.get('/session', async (req, res) => {
-    if (argv._.includes('firefox')) {
-        objects.browser = await firefox.launch( { "headless" : !argv.visible } );
-    }
-    if (argv._.includes('chrome')) {
-        objects.browser = await chromium.launch( { "headless" : !argv.visible } );
-    }
-    if (argv._.includes('webkit')) {
-        objects.browser = await webkit.launch( { "headless" : !argv.visible } );
-    }
+app.post('/session', async (req, res) => {
+	var payload = req.body;
+    var type    = payload.type;
+    var args    = payload.args || [];
 
-    if (!objects.browser) {
-        console.log('no browser selected, begone');
-        process.exit(1);
-    }
+    console.log(type,args);
 
-    if (argv.debug) {
-        console.log('Browser Ready for use');
+    var result;
+    if ( type && browsers[type] ) {
+        try {
+            var browser = await firefox.launch(...args);
+            objects[browser._guid] = browser;
+            result = { error : false, message : browser };
+        } catch (e) {
+            result = { error : true, message : e.message};
+        }
+    } else {
+        result = { error : true, message : "Please select a supported browser" };
     }
-    res.json({ error: false, message: 'Browser started successfully.' });
+    res.json(result);
 });
 
 app.post('/command', async (req, res) => {
 
-	var payload = req.body;
+    var payload = req.body;
     var type    = payload.type;
     var object  = payload.object;
     var command = payload.command;
@@ -124,7 +99,7 @@ app.get('/shutdown', async (req, res) => {
 if (require.main === module) {
     app.listen( port, () => {
         if (argv.debug) {
-	        console.log(`Listening on port ${port}`);
+            console.log(`Listening on port ${port}`);
         }
     });
 }

+ 3 - 1
example.pl

@@ -2,9 +2,11 @@ use strict;
 use warnings;
 
 use Data::Dumper;
+use JSON::PP;
 use Playwright;
 
-my ($browser) = Playwright->new( browser => 'chrome', visible => 1 );
+my $handle = Playwright->new();
+my $browser = $handle->launch( headless => JSON::PP::false, type => 'chrome' );
 my $page = $browser->newPage();
 my $res = $page->goto('http://google.com', { waitUntil => 'networkidle' });
 print Dumper($res->status(), $browser->version());

+ 86 - 51
lib/Playwright.pm

@@ -7,14 +7,13 @@ use sigtrap qw/die normal-signals/;
 
 use File::Basename();
 use Cwd();
-use Net::EmptyPort();
 use LWP::UserAgent();
-use Sub::Install();
+use Net::EmptyPort();
 use JSON::MaybeXS();
 use File::Slurper();
 use Carp qw{confess};
 
-use Playwright::Page();
+use Playwright::Util();
 
 #ABSTRACT: Perl client for Playwright
 
@@ -66,15 +65,7 @@ Creates a new browser and returns a handle to interact with it.
 
 =cut
 
-our ($spec, $server_bin, %class_spec);
-
-my %transmogrify = (
-    Page   => sub { 
-        my ($self, $res) = @_;
-        require Playwright::Page;
-        return Playwright::Page->new( browser => $self, id => $res->{_guid} );
-    },
-);
+our ($spec, $server_bin, %mapper);
 
 BEGIN {
     my $path2here = File::Basename::dirname(Cwd::abs_path($INC{'Playwright.pm'}));
@@ -84,23 +75,18 @@ BEGIN {
     my $spec_raw = File::Slurper::read_text($specfile);
     my $decoder = JSON::MaybeXS->new();
     $spec = $decoder->decode($spec_raw);
-    %class_spec = (
-        %{$spec->{Browser}{members}},
-        %{$spec->{BrowserContext}{members}}
-    );
-
-    # Install the subroutines if they aren't already
-    foreach my $method (keys(%class_spec)) {
-        Sub::Install::install_sub({
-            code => sub { _request(shift, \%transmogrify, args => [@_], command => $method, type => 'Browser', object => 'browser' ) },
-            as   => $method,
-        });
+
+    foreach my $class (keys(%$spec)) {
+        $mapper{$class} = sub {
+            my ($self, $res) = @_;
+            my $class = "Playwright::$class";
+            return $class->new( handle => $self, id => $res->{_guid}, type => $class );
+        };
     }
 
     # Make sure it's possible to start the server
     $server_bin = "$path2here/../bin/playwright.js";
     confess("Can't locate Playwright server in '$server_bin'!") unless -f $specfile;
-    1;
 }
 
 sub new ($class, %options) {
@@ -110,27 +96,30 @@ sub new ($class, %options) {
     my $self = bless({
         spec    => $spec,
         ua      => $options{ua} // LWP::UserAgent->new(),
-        browser => $options{browser},
-        visible => $options{visible},
         port    => $port,
         debug   => $options{debug},
-        pid     => _start_server($options{browser},$options{visible}, $port, $options{debug}),
+        pid     => _start_server( $port, $options{debug}),
     }, $class);
 
-    $self->_request( \%transmogrify, url => 'session' );
     return $self;
 }
 
 =head1 METHODS
 
-=head2 spec
+=head2 launch(HASH) = Playwright::Browser
+
+The Argument hash here is essentially those you'd see from browserType.launch().  See:
+L<https://playwright.dev/#version=v1.5.1&path=docs%2Fapi.md&q=browsertypelaunchoptions>
 
-Return the relevant methods and their definitions for this module which are built dynamically from the Playwright API spec.
+There is an additional "special" argument, that of 'type', which is used to specify what type of browser to use, e.g. 'firefox'.
 
 =cut
 
-sub spec ($self) {
-    return %class_spec;
+sub launch ($self, %args) {
+    #TODO coerce types based on spec
+    my $msg = Playwright::Util::request ('POST', 'session', $self->{port}, $self->{ua}, type => delete $args{type}, args => [\%args] );
+    return $Playwright::mapper{$msg->{_type}}->($self,$msg) if (ref $msg eq 'HASH') && $msg->{_type} && exists $Playwright::mapper{$msg->{_type}};
+    return $msg;
 }
 
 =head2 quit, DESTROY
@@ -142,7 +131,7 @@ Automatically called when the Playwright object goes out of scope.
 =cut
 
 sub quit ($self) {
-    $self->_request( \%transmogrify, url => 'shutdown' );
+    Playwright::Util::request ('GET', 'shutdown', $self->{port}, $self->{ua} ); 
     return waitpid($self->{pid},0);
 }
 
@@ -150,9 +139,7 @@ sub DESTROY ($self) {
     $self->quit();
 }
 
-sub _start_server($browser,$visible, $port, $debug) {
-    confess("Invalid browser '$browser'") unless grep { $_ eq $browser } qw{chrome firefox webkit};
-    $visible = $visible ? '-v' : '';
+sub _start_server($port, $debug) {
     $debug   = $debug   ? '-d' : '';
 
     $ENV{DEBUG} = 'pw:api';
@@ -164,27 +151,75 @@ sub _start_server($browser,$visible, $port, $debug) {
         return $pid;
     }
 
-    exec( $server_bin, $browser, $visible, "-p", $port, $debug);
+    exec( $server_bin, "-p", $port, $debug);
 }
 
-sub _request ($self, $translator, %args) {
-    $translator //= \%transmogrify;
-    my $url = $args{url} // 'command';
-    my $fullurl = "http://localhost:$self->{port}/$url";
+1;
 
-    my $method = $url eq 'command' ? 'POST' : 'GET';
+#TODO just define these based on the dang JSON
 
-    my $request  = HTTP::Request->new( $method, $fullurl);
-    $request->header( 'Content-type' => 'application/json' );
-    $request->content( JSON::MaybeXS::encode_json(\%args) );
-    my $response = $self->{ua}->request($request);
-    my $decoded  = JSON::MaybeXS::decode_json($response->decoded_content());
-    my $msg = $decoded->{message};
+package Playwright::Browser;
 
-    confess($msg) if $decoded->{error};
+use parent qw{Playwright::Base};
 
-    return $translator->{$msg->{_type}}->($self,$msg) if (ref $msg eq 'HASH') && $msg->{_type} && exists $translator->{$msg->{_type}};
-    return $msg;
+sub new ($class,%options) {
+    $options{type} = 'Browser';
+    return $class->SUPER::new(%options);
+}
+
+1;
+
+package Playwright::BrowserContext;
+
+use parent qw{Playwright::Base};
+
+sub new ($class,%options) {
+    $options{type} = 'BrowserContext';
+    $class->SUPER::new(%options);
+}
+
+1;
+
+package Playwright::Page;
+
+use parent qw{Playwright::Base};
+
+sub new ($class,%options) {
+    $options{type} = 'Page';
+    $class->SUPER::new(%options);
+}
+
+1;
+
+package Playwright::Frame;
+
+use parent qw{Playwright::Base};
+
+sub new ($class,%options) {
+    $options{type} = 'Frame';
+    $class->SUPER::new(%options);
+}
+
+1;
+
+package Playwright::Response;
+
+use parent qw{Playwright::Base};
+
+sub new ($class,%options) {
+    $options{type} = 'Response';
+    $class->SUPER::new(%options);
+}
+
+1;
+
+package Playwright::ElementHandle;
+
+use parent qw{Playwright::Base};
+
+sub new ($class,%options) {
+    $options{type} = 'Result';
+    $class->SUPER::new(%options);
 }
 
 1;

+ 91 - 0
lib/Playwright/Base.pm

@@ -0,0 +1,91 @@
+package Playwright::Base;
+
+use strict;
+use warnings;
+
+use Sub::Install();
+
+use Playwright::Util();
+
+#ABSTRACT: Object representing Playwright pages
+
+no warnings 'experimental';
+use feature qw{signatures};
+
+=head2 SYNOPSIS
+
+    use Playwright;
+    my ($handle,$page) = Playwright->new( handle => "chrome" );
+    $page->goto('http://www.google.com');
+    my $handle_version = $handle->version();
+    $handle->quit();
+
+=head2 DESCRIPTION
+
+Base class for each Playwright class.
+You probably shouldn't use this directly; instead use a subclass.
+
+The specification for each class can also be inspected with the 'spec' method:
+
+    use Data::Dumper;
+    my $page = Playwright::Base->new(...);
+    print Dumper($page->spec('Page'));
+
+=head1 CONSTRUCTOR
+
+=head2 new(HASH) = (Playwright::Base)
+
+Creates a new page and returns a handle to interact with it.
+
+=head3 INPUT
+
+    handle (Playwright) : Playwright object.
+    spec (HASHREF)      : Specification for the class to build.
+    id (STRING)         : _guid returned by a response from the Playwright server with the provided type.
+    type (STRING)       : Type to actually use
+
+=cut
+
+sub new ($class, %options) {
+
+    my $self = bless({
+        spec    => $Playwright::spec->{$options{type}}{members},
+        type    => $options{type},
+        guid    => $options{id},
+        ua      => $options{handle}{ua},
+        port    => $options{handle}{port},
+    }, $class);
+
+    # Install the subroutines if they aren't already
+    foreach my $method (keys(%{$self->{spec}})) {
+        Sub::Install::install_sub({
+            code => sub {
+                my $self = shift;
+                $self->_request( args => [@_], command => $method, object => $self->{guid}, type => $self->{type} )
+            },
+            as   => $method,
+        }) unless $self->can($method);
+    }
+
+    return ($self);
+}
+
+=head1 METHODS
+
+=head2 spec
+
+Return the relevant methods and their definitions for this module which are built dynamically from the Playwright API spec.
+
+=cut
+
+sub spec ($self) {
+    return %{$self->{spec}};
+}
+
+sub _request ($self, %args) {
+    my $msg = Playwright::Util::request ('POST', 'command', $self->{port}, $self->{ua}, %args);
+    return $Playwright::mapper{$msg->{_type}}->($self,$msg) if (ref $msg eq 'HASH') && $msg->{_type} && exists $Playwright::mapper{$msg->{_type}};
+    return $msg;
+}
+
+1;

+ 0 - 98
lib/Playwright/Frame.pm

@@ -1,98 +0,0 @@
-package Playwright::Frame;
-
-use strict;
-use warnings;
-
-use Sub::Install();
-use Carp qw{confess};
-
-#ABSTRACT: Object representing Playwright pages
-
-no warnings 'experimental';
-use feature qw{signatures state};
-
-=head2 SYNOPSIS
-
-    use Playwright;
-    my ($browser,$page) = Playwright->new( browser => "chrome" );
-    $page->goto('http://www.google.com');
-    my $browser_version = $browser->version();
-    $browser->quit();
-
-=head2 DESCRIPTION
-
-Perl interface to a lightweight node.js webserver that proxies commands runnable by Playwright in the 'Frame' Class.
-See L<https://playwright.dev/#version=master&path=docs%2Fapi.md&q=class-frame> for more information.
-
-The specification for this class can also be inspected with the 'spec' method:
-
-    use Data::Dumper;
-    my $page = Playwright::Page->new(...);
-    print Dumper($page->spec);
-
-=head1 CONSTRUCTOR
-
-=head2 new(HASH) = (Playwright::Frame)
-
-Creates a new page and returns a handle to interact with it, along with a Playwright::Frame (the main Frame) to interact with (supposing the page is a FrameSet).
-
-=head3 INPUT
-
-    browser (Playwright) : Playwright object.
-    page (STRING) : _guid returned by a response from the Playwright server with _type of 'Page'.
-
-=cut
-
-my %transmogrify = (
-    Frame         => sub {
-        my ($self, $res) = @_;
-        require Playwright::Frame;
-        return Playwright::Frame->new( browser => $self, id => $res->{_guid} );
-    },
-    ElementHandle => sub {
-        my ($self, $res) = @_;
-        require Playwright::Element;
-        return Playwright::Element->new( browser => $self, id    => $res->{_guid} ); 
-    },
-    Response => sub {
-        my ($self, $res) = @_;
-        require Playwright::Response;
-        return Playwright::Response->new( browser => $self, id   => $res->{_guid} );
-    },
-);
-
-sub new ($class, %options) {
-
-    my $self = bless({
-        spec    => $options{browser}{spec}{Frame}{members},
-        browser => $options{browser},
-        guid    => $options{id},
-    }, $class);
-
-    # Install the subroutines if they aren't already
-    foreach my $method (keys(%{$self->{spec}})) {
-        Sub::Install::install_sub({
-            code => sub {
-                my $self = shift;
-                $self->{browser}->_request( \%transmogrify, args => [@_], command => $method, object => $self->{guid}, type => 'Frame' )
-            },
-            as   => $method,
-        }) unless $self->can($method);
-    }
-
-    return ($self);
-}
-
-=head1 METHODS
-
-=head2 spec
-
-Return the relevant methods and their definitions for this module which are built dynamically from the Playwright API spec.
-
-=cut
-
-sub spec ($self) {
-    return %{$self->{spec}};
-}
-
-1;

+ 0 - 98
lib/Playwright/Page.pm

@@ -1,98 +0,0 @@
-package Playwright::Page;
-
-use strict;
-use warnings;
-
-use Sub::Install();
-use Carp qw{confess};
-
-#ABSTRACT: Object representing Playwright pages
-
-no warnings 'experimental';
-use feature qw{signatures state};
-
-=head2 SYNOPSIS
-
-    use Playwright;
-    my ($browser,$page) = Playwright->new( browser => "chrome" );
-    $page->goto('http://www.google.com');
-    my $browser_version = $browser->version();
-    $browser->quit();
-
-=head2 DESCRIPTION
-
-Perl interface to a lightweight node.js webserver that proxies commands runnable by Playwright in the 'Page' Class.
-See L<https://playwright.dev/#version=master&path=docs%2Fapi.md&q=class-page> for more information.
-
-The specification for this class can also be inspected with the 'spec' method:
-
-    use Data::Dumper;
-    my $page = Playwright::Page->new(...);
-    print Dumper($page->spec);
-
-=head1 CONSTRUCTOR
-
-=head2 new(HASH) = (Playwright::Page)
-
-Creates a new page and returns a handle to interact with it.
-
-=head3 INPUT
-
-    browser (Playwright) : Playwright object.
-    page (STRING) : _guid returned by a response from the Playwright server with _type of 'Page'.
-
-=cut
-
-my %transmogrify = (
-    Frame         => sub {
-        my ($self, $res) = @_;
-        require Playwright::Frame;
-        return Playwright::Frame->new( browser => $self, id => $res->{_guid} );
-    },
-    ElementHandle => sub {
-        my ($self, $res) = @_;
-        require Playwright::Element;
-        return Playwright::Element->new( browser => $self, id    => $res->{_guid} ); 
-    },
-    Response => sub {
-        my ($self, $res) = @_;
-        require Playwright::Response;
-        return Playwright::Response->new( browser => $self, id   => $res->{_guid} );
-    },
-);
-
-sub new ($class, %options) {
-
-    my $self = bless({
-        spec    => $options{browser}{spec}{Page}{members},
-        browser => $options{browser},
-        guid    => $options{id},
-    }, $class);
-
-    # Install the subroutines if they aren't already
-    foreach my $method (keys(%{$self->{spec}})) {
-        Sub::Install::install_sub({
-            code => sub { 
-                my $self = shift;
-                $self->{browser}->_request( \%transmogrify, args => [@_], command => $method, object => $self->{guid}, type => 'Page' )
-            },
-            as   => $method,
-        }) unless $self->can($method);
-    }
-
-    return ($self);
-}
-
-=head1 METHODS
-
-=head2 spec
-
-Return the relevant methods and their definitions for this module which are built dynamically from the Playwright API spec.
-
-=cut
-
-sub spec ($self) {
-    return %{$self->{spec}};
-}
-
-1;

+ 0 - 80
lib/Playwright/Response.pm

@@ -1,80 +0,0 @@
-package Playwright::Response;
-
-use strict;
-use warnings;
-
-use Sub::Install();
-use Carp qw{confess};
-
-#ABSTRACT: Object representing Playwright network responses
-
-no warnings 'experimental';
-use feature qw{signatures state};
-
-=head2 SYNOPSIS
-
-    use Playwright;
-    my ($browser,$page) = Playwright->new( browser => "chrome" );
-    my $res = $page->goto('http://www.google.com');
-    print $res->url;
-
-=head2 DESCRIPTION
-
-Perl interface to a lightweight node.js webserver that proxies commands runnable by Playwright in the 'Response' Class.
-See L<https://playwright.dev/#version=master&path=docs%2Fapi.md&q=class-response> for more information.
-
-The specification for this class can also be inspected with the 'spec' method:
-
-    use Data::Dumper;
-    use Playwright::Response;
-    my $page = Playwright::Response->new(...);
-    print Dumper($page->spec);
-
-=head1 CONSTRUCTOR
-
-=head2 new(HASH) = (Playwright::Response)
-
-Creates a new page and returns a handle to interact with it, along with a Playwright::Frame (the main Frame) to interact with (supposing the page is a FrameSet).
-
-=head3 INPUT
-
-    browser (Playwright) : Playwright object.
-    page (STRING) : _guid returned by a response from the Playwright server with _type of 'Page'.
-
-=cut
-
-sub new ($class, %options) {
-
-    my $self = bless({
-        spec    => $options{browser}{spec}{Response}{members},
-        browser => $options{browser},
-        guid    => $options{id},
-    }, $class);
-
-    # Install the subroutines if they aren't already
-    foreach my $method (keys(%{$self->{spec}})) {
-        Sub::Install::install_sub({
-            code => sub {
-                my $self = shift;
-                $self->{browser}->_request( undef, args => [@_], command => $method, object => $self->{guid}, type => 'Response' );
-            },
-            as   => $method,
-        }) unless $self->can($method);
-    }
-
-    return ($self);
-}
-
-=head1 METHODS
-
-=head2 spec
-
-Return the relevant methods and their definitions for this module which are built dynamically from the Playwright API spec.
-
-=cut
-
-sub spec ($self) {
-    return %{$self->{spec}};
-}
-
-1;

+ 36 - 0
lib/Playwright/Util.pm

@@ -0,0 +1,36 @@
+package Playwright::Util;
+
+use strict;
+use warnings;
+
+use JSON::MaybeXS();
+use Carp qw{confess};
+
+#ABSTRACT: Common utility functions for the Playwright module
+
+no warnings 'experimental';
+use feature qw{signatures};
+
+=head2 request(STRING method, STRING url, INTEGER port, LWP::UserAgent ua, HASH args) = HASH
+
+De-duplicates request logic in the Playwright Modules.
+
+=cut
+
+sub request ($method, $url, $port, $ua, %args) {
+    my $fullurl = "http://localhost:$port/$url";
+
+    my $request  = HTTP::Request->new( $method, $fullurl);
+    $request->header( 'Content-type' => 'application/json' );
+    $request->content( JSON::MaybeXS::encode_json(\%args) );
+    my $response = $ua->request($request);
+    my $content =  $response->decoded_content();
+    my $decoded  = JSON::MaybeXS::decode_json($content);
+    my $msg = $decoded->{message};
+
+    confess($msg) if $decoded->{error};
+
+    return $msg;
+}
+
+1;