Bläddra i källkod

Perl module working, whee

George S. Baugh 5 år sedan
förälder
incheckning
5eb49df459
3 ändrade filer med 282 tillägg och 9 borttagningar
  1. 17 8
      bin/playwright.js
  2. 170 1
      lib/Playwright.pm
  3. 95 0
      lib/Playwright/Page.pm

+ 17 - 8
bin/playwright.js

@@ -36,6 +36,11 @@ const argv = yargs
             type: 'boolean',
         }
     })
+    .option('debug', {
+        alias: 'd',
+        description: 'Print additional debugging messages',
+        type: 'boolean',
+    })
     .option('port', {
         alias: 'p',
         description: 'Run on specified port',
@@ -70,16 +75,20 @@ var responses = {};
     }
 
     if (!browser) {
-        console.log(argv);
         console.log('no browser selected, begone');
         process.exit(1);
     }
     pages.default = await browser.newPage();
     pages.default.goto('http://google.com');
-    console.log('Browser Ready for use');
+
+    if (argv.debug) {
+        console.log('Browser Ready for use');
+    }
 
 })();
 
+
+
 app.use(express.json())
 app.get('/command', async (req, res) => {
 
@@ -98,8 +107,8 @@ app.get('/command', async (req, res) => {
         // Operate on the provided page
         const res = await pages[page][command](...args);
         result = { error : false, message : res };
-    } else if ( results[result] && spec.Result.members[command]) {
-        const res = await results[result][command]
+    } else if ( responses[result] && spec.Result.members[command]) {
+        const res = await responses[result][command]
         result = { error : false, message : res };
     } else if ( spec.Browser.members[command] || spec.BrowserContext.members[command] ) {
         const res = await browser[command](...args);
@@ -121,16 +130,16 @@ app.get('/command', async (req, res) => {
 });
 
 app.get('/shutdown', async (req, res) => {
-    console.log('shutting down...');
     await browser.close();
-    console.log('done');
-    res.send("Sent kill signal to browser");
+    res.json( { error: false, message : "Sent kill signal to browser" });
     process.exit(0);
 });
 
 //Modulino
 if (require.main === module) {
     app.listen( port, () => {
-	    console.log(`Listening on port ${port}`);
+        if (argv.debug) {
+	        console.log(`Listening on port ${port}`);
+        }
     });
 }

+ 170 - 1
lib/Playwright.pm

@@ -3,11 +3,180 @@ package Playwright;
 use strict;
 use warnings;
 
+use sigtrap qw/die normal-signals/;
+
+use File::Basename();
+use Cwd();
+use URI::Query();
+use Net::EmptyPort();
+use LWP::UserAgent();
+use Sub::Install();
+use JSON::MaybeXS();
+use File::Slurper();
+use Carp qw{confess};
+
+use Playwright::Page();
+
+#ABSTRACT: Perl client for Playwright
+
 no warnings 'experimental';
 use feature qw{signatures state};
 
-sub new (%options) {
+=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.
+Currently understands commands you can send to the following Playwright classes,
+commands for which can be sent via instances of the noted module
+
+=over 4
+
+=item B<Browser> - L<Playwright> L<https://playwright.dev/#version=master&path=docs%2Fapi.md&q=class-browser>
+
+=item B<BrowserContext> - L<Playwright> L<https://playwright.dev/#version=master&path=docs%2Fapi.md&q=class-browsercontext>
+
+=item B<Page> - L<Playwright::Page> L<https://playwright.dev/#version=v1.5.1&path=docs%2Fapi.md&q=class-page>
+
+=item B<Result> - L<Playwright::Result> L<https://playwright.dev/#version=v1.5.1&path=docs%2Fapi.md&q=class-response>
+
+=back
+
+The specification for the above classes can also be inspected with the 'spec' method for each respective class:
+
+    use Data::Dumper;
+    print Dumper($browser->spec , $page->spec, ...);
+
+=head1 CONSTRUCTOR
+
+=head2 new(HASH) = (Playwright,Playwright::Page)
+
+Creates a new browser and returns a handle to interact with it, along with a new page Handle to interact with therein.
+
+=head3 INPUT
+
+    browser (STRING) : Name of the browser to use.  One of (chrome, firefox, webkit).
+    visible (BOOL)   : Whether to start the browser such that it displays on your desktop (headless or not).
+    debug   (BOOL)   : Print extra messages from the Playwright server process
+
+=cut
+
+our ($spec, $server_bin, %class_spec);
+
+BEGIN {
+    my $path2here = File::Basename::dirname(Cwd::abs_path($INC{'Playwright.pm'}));
+    my $specfile = "$path2here/../api.json";
+    confess("Can't locate Playwright specification in '$specfile'!") unless -f $specfile;
+
+    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(@_) },
+            as   => $method,
+        });
+    }
+
+    # 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) {
+
+    #XXX yes, this is a race, so we need retries in _start_server
+    my $port = Net::EmptyPort::empty_port();
+    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}),
+    }, $class);
+
+    return ($self, Playwright::Page->new( browser => $self, page => 'default' ));
+}
+
+=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 %class_spec;
+}
+
+=head2 quit, DESTROY
+
+Terminate the browser session and wait for the Playwright server to terminate.
+
+Automatically called when the Playwright object goes out of scope.
+
+=cut
+
+sub quit ($self) {
+    $self->_request( url => 'shutdown' );
+    return waitpid($self->{pid},0);
+}
+
+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' : '';
+    $debug   = $debug   ? '-d' : '';
+
+    my $pid = fork // confess("Could not fork");
+    return $pid if $pid;
+
+    exec( $server_bin, $browser, $visible, "-p", $port, $debug);
+}
+
+my %transmogrify = (
+    Page   => sub { 
+        my ($self, $res) = @_;
+        require Playwright::Page;
+        return Playwright::Page->new(   browser => $self, page => $res->{_guid} );
+    },
+    Result => sub {
+        my ($self, $res) = @_;
+        require Playwright::Result;
+        return Playwright::Result->new( browser => $self, id   => $res->{_guid} );
+    },
+);
+
+sub _request ($self, %args) {
+    my $qq = URI::Query->new(%args);
+    my $url = $args{url} // 'command';
+    my $fullurl = "http://localhost:$self->{port}/$url?$qq";
 
+    my $request  = HTTP::Request->new( 'GET', $fullurl );#, $header, $content );
+    my $response = $self->{ua}->request($request);
+    my $decoded  = JSON::MaybeXS::decode_json($response->decoded_content());
+    
+    return $transmogrify{$decoded->{_type}}->($self,$decoded) if $decoded->{_type} && exists $transmogrify{$decoded->{_type}};
+    return $decoded;
 }
 
 1;

+ 95 - 0
lib/Playwright/Page.pm

@@ -0,0 +1,95 @@
+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=v1.5.1&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,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
+
+sub new ($class, %options) {
+
+    my $self = bless({
+        spec    => $options{browser}{spec}{Page}{members},
+        browser => $options{browser},
+        guid    => $options{page},
+    }, $class);
+
+    # Install the subroutines if they aren't already
+    foreach my $method (keys(%{$self->{spec}})) {
+        Sub::Install::install_sub({
+            code => sub { _request(@_) },
+            as   => $method,
+        }) unless $self->can($method);
+    }
+
+    return ($self);#, $self->mainFrame());
+}
+
+=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}};
+}
+
+my %transmogrify = (
+    Frame         => sub {
+        my ($self, $res) = @_;
+        require Playwright::Frame;
+        return Playwright::Frame->new(   page => $self, frame => $res->{_guid} );
+    },
+    ElementHandle => sub {
+        my ($self, $res) = @_;
+        require Playwright::Element;
+        return Playwright::Element->new( page => $self, id    => $res->{_guid} ); 
+    },
+);
+
+sub _request ($self,%options) {
+    $options{page} = $self->{guid};
+    return $self->SUPER::_request(%options);
+}
+
+1;