George S. Baugh 4 anni fa
parent
commit
9c38f26f6d
17 ha cambiato i file con 541 aggiunte e 215 eliminazioni
  1. 0 51
      Changes
  2. 1 0
      Changes
  3. 1 1
      LICENSE
  4. 3 0
      MANIFEST.SKIP
  5. 6 2
      README.md
  6. 0 26
      bin/playwright_server
  7. 8 0
      clean_generated_files.sh
  8. 55 0
      conf/Changes
  9. 1 0
      conf/perlcriticrc
  10. 31 11
      dist.ini
  11. 6 0
      generate_api_json.sh
  12. 178 0
      generate_perl_modules.pl
  13. 15 91
      lib/Playwright.pm
  14. 0 1
      perlcriticrc
  15. 1 0
      perlcriticrc
  16. 234 0
      playwright_server
  17. 1 32
      t/Playwright.t

+ 0 - 51
Changes

@@ -1,51 +0,0 @@
-Revision history for Playwright
-
-0.012 2021-08-03 TEODESIAN
-    - Automatically translate element handles passed as args objects to the playwright process.
-
-0.011 2021-07-27 TEODESIAN
-    - Make no attempts whatsoever to install node deps for users, instead giving them advice how to self-service fix their problems.
-
-0.010 2021-07-27 TEODESIAN
-    - Fix issue with yargs fix breaking invocation in Playwright.pm
-    - Fix issue with child selectors being broken
-    - Add ability to specify library path
-
-0.009 2021-07-26 TEODESIAN
-    - Remove dependency on yargs in bin/playwright_server
-
-0.008 2021-07-16 TEODESIAN
-    - Add parent attribute to grab element parents when needed
-    - Remove dependency on AsyncData in favor of File::Temp, Sereal and fork().
-    - Prevent destructors for other objects firing in forks used to do asynchronous operations.
-
-0.007 2021-06-17 TEODESIAN
-    - Adjust module for changing Download returns, and api.json no longer being shipped with Playwright
-    - Fix some warnings when installing for the first time.
-
-0.006 2021-04-12 TEODESIAN
-    - Prevent $? from bubbling up in our destructor and invalidating program exit code by localizing $? in quit()
-    - Add a link to the Playwright slack in the documentation.
-
-0.005 2021-03-24 TEODESIAN
-    [BUG FIXES]
-    - Prevent double destroy in the event of quit() being called
-    - Make the destroy() process a good deal more reliable
-    - Add a timeout parameter to new() to control how long to wait for the server to spin up/down
-    - Improve documentation
-    - Adjust auto-install process to work better on windows, and not leak stderr in some contexts.
-
-0.004 2021-03-19 TEODESIAN
-    [BUG FIXES]
-    - Adjust spec parser for newer Playwright spec versions
-
-0.003 2021-03-16 TEODESIAN
-    [BUG FIXES]
-    - Fix broken testsuite
-
-0.002 2021-02-10 TEODESIAN
-    [BUG FIXES]
-    - Declare perl 5.28 to be minimum version
-
-0.001 2020-11-02 TEODESIAN
-    - First release to CPAN

+ 1 - 0
Changes

@@ -0,0 +1 @@
+conf/Changes

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 Troglodyne LLC
+Copyright (c) 2021 Troglodyne LLC
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 3 - 0
MANIFEST.SKIP

@@ -13,3 +13,6 @@ example.pl
 tidyall.ini
 generate_api_json.sh
 api.json
+playwright_server
+generate_perl_modules.pl
+clean_generated_files.sh

+ 6 - 2
README.md

@@ -46,11 +46,13 @@ dzil authordeps --missing | sudo cpanm
 dzil listdeps --missing | sudo cpanm
 ```
 
-Actually running stuff:
+Running dzil test should let you know if your kit is good to develop.
+
+Actually running stuff will look like this after you generate the API json and modules:
 
 `PATH="$(pwd)/bin:$PATH" perl -Ilib example.pl`
 
-## Dealing with api.json
+## Dealing with api.json and generating modules
 
 Playwright doesn't ship their api.json with the distribution on NPM.
 You have to generate it from their repo.
@@ -58,6 +60,8 @@ You have to generate it from their repo.
 clone it in a directory that is the same as the one containing this repository.
 then run `generate_api_json.sh` to get things working such that the build scripts know what to do.
 
+Then run `generate_perl_modules.pl` to get the accessor classes built based off of the spec, and insert the spec JSON into the playwright_server binary.
+
 ## Questions?
 Hop into the playwright slack, and check out the #playwright-perl channel therein.
 I'm watching that space and should be able to answer your questions.

File diff suppressed because it is too large
+ 0 - 26
bin/playwright_server


+ 8 - 0
clean_generated_files.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+rm api.json; /bin/true
+rm bin/playwright_server; /bin/true
+rmdir bin; /bin/true
+for module in $(git ls-files -o --exclude-standard lib/Playwright)
+do
+    rm $module; /bin/true
+done

+ 55 - 0
conf/Changes

@@ -0,0 +1,55 @@
+Revision history for Playwright
+
+0.013 2021-08-31 TEODESIAN
+    - Statically generate playwright subclasses so that callers can easily wrap them with MOPs.
+    - allow evaluate() to be called on ElementHandles
+
+0.012 2021-08-03 TEODESIAN
+    - Automatically translate element handles passed as args objects to the playwright process.
+
+0.011 2021-07-27 TEODESIAN
+    - Make no attempts whatsoever to install node deps for users, instead giving them advice how to self-service fix their problems.
+
+0.010 2021-07-27 TEODESIAN
+    - Fix issue with yargs fix breaking invocation in Playwright.pm
+    - Fix issue with child selectors being broken
+    - Add ability to specify library path
+
+0.009 2021-07-26 TEODESIAN
+    - Remove dependency on yargs in bin/playwright_server
+
+0.008 2021-07-16 TEODESIAN
+    - Add parent attribute to grab element parents when needed
+    - Remove dependency on AsyncData in favor of File::Temp, Sereal and fork().
+    - Prevent destructors for other objects firing in forks used to do asynchronous operations.
+
+0.007 2021-06-17 TEODESIAN
+    - Adjust module for changing Download returns, and api.json no longer being shipped with Playwright
+    - Fix some warnings when installing for the first time.
+
+0.006 2021-04-12 TEODESIAN
+    - Prevent $? from bubbling up in our destructor and invalidating program exit code by localizing $? in quit()
+    - Add a link to the Playwright slack in the documentation.
+
+0.005 2021-03-24 TEODESIAN
+    [BUG FIXES]
+    - Prevent double destroy in the event of quit() being called
+    - Make the destroy() process a good deal more reliable
+    - Add a timeout parameter to new() to control how long to wait for the server to spin up/down
+    - Improve documentation
+    - Adjust auto-install process to work better on windows, and not leak stderr in some contexts.
+
+0.004 2021-03-19 TEODESIAN
+    [BUG FIXES]
+    - Adjust spec parser for newer Playwright spec versions
+
+0.003 2021-03-16 TEODESIAN
+    [BUG FIXES]
+    - Fix broken testsuite
+
+0.002 2021-02-10 TEODESIAN
+    [BUG FIXES]
+    - Declare perl 5.28 to be minimum version
+
+0.001 2020-11-02 TEODESIAN
+    - First release to CPAN

+ 1 - 0
conf/perlcriticrc

@@ -0,0 +1 @@
+exclude = RequireUseStrict|RequireUseWarnings|ProhibitSubroutinePrototypes

+ 31 - 11
dist.ini

@@ -1,14 +1,39 @@
 name = Playwright
-version = 0.012
+version = 0.013
 author = George S. Baugh <george@troglodyne.net>
 license = MIT
 copyright_holder = Troglodyne LLC
-copyright_year = 2020
+copyright_year = 2021
 
-[GatherDir]
-include_dotfiles = 1
+[Run::BeforeBuild]
+run = npm i
+run = sudo npx playwright install-deps
+run = ./generate_api_json.sh
+run = ./generate_perl_modules.pl
+run = PATH="$(pwd)/bin:$PATH" perl -Ilib example.pl
+
+[Run::AfterBuild]
+run = ./clean_generated_files.sh
+
+[GatherDir / LibFiles ]
+include_untracked = 1
 exclude_match = .*\.swp
 exclude_match = .*\.swo
+root = ./lib
+prefix = lib
+
+[GatherDir / BuildConf ]
+root = ./conf
+prefix = .
+
+[GatherDir / BinFiles ]
+include_untracked = 1
+root = ./bin
+prefix = bin
+
+[GatherDir / TestFiles ]
+root = ./t
+prefix = t
 
 [PruneCruft]
 except = \.travis.yml
@@ -16,6 +41,7 @@ except = \.travis.yml
 [ManifestSkip]
 [MetaYAML]
 [MetaJSON]
+[License]
 [Readme]
 [InstallGuide]
 [ExtraTests]
@@ -33,6 +59,7 @@ finder = :InstallModules ;
 
 [PodWeaver]
 finder=NoBin
+
 [Git::Contributors]
 [TidyAll]
 
@@ -54,19 +81,12 @@ disable = Test::Synopsis
 
 [Prereqs / RuntimeRequires]
 perl = 5.010
-Moo = 1.005
 List::Util = 1.33
 
 [GithubMeta]
 issues = 1
 user = teodesian
 
-[Encoding]
-filename = t/www/icon.gif
-filename = t/www/invalid-extension.xpi
-filename = t/www/redisplay.xpi
-encoding = bytes
-
 ; `dzil authordeps` doesn't know about the Pod Weaver dependencies:
 ; authordep Pod::Weaver::Section::Contributors = 0
 ; authordep Pod::Weaver::Plugin::Encoding = 0

+ 6 - 0
generate_api_json.sh

@@ -1,5 +1,11 @@
 #!/bin/bash
+# Relies on the user having cloned the playwright repository in the directory directly adjacent to this repo
+./clean_generated_files.sh
+mkdir bin; /bin/true
 pushd ../playwright
 git pull
 node utils/doclint/generateApiJson.js > ../playwright-perl/api.json
 popd
+cp playwright_server bin/playwright_server
+API="$(<api.json)"
+sed -i -e '/%REPLACEME%/r api.json' -e 's/%REPLACEME%//g' bin/playwright_server

+ 178 - 0
generate_perl_modules.pl

@@ -0,0 +1,178 @@
+#!/usr/bin/perl
+
+# The point of this is to build skeleton classes which are then fleshed out at runtime
+# so that people can wrap a mop around it
+
+use strict;
+use warnings;
+
+use FindBin;
+use File::Slurper;
+use JSON;
+
+use lib "$FindBin::Bin/lib";
+use Playwright::Util;
+
+my $module_source = '';
+while (<DATA>) {
+    $module_source .= $_;
+}
+
+# Next, grab the API JSON and iterate to build classes.
+our $raw = File::Slurper::read_binary("$FindBin::Bin/api.json");
+our $spec = JSON::decode_json($raw);
+$spec = Playwright::Util::arr2hash($spec,'name');
+
+our %mapper = (
+    mouse     =>  "
+=head2 mouse()
+
+Returns a Playwright::Mouse object.
+
+=cut
+
+sub mouse {
+    my ( \$self ) = \@_;
+    return Playwright::Mouse->new(
+        handle => \$self,
+        parent => \$self,
+        id     => \$self->{guid},
+    );
+}\n\n",
+    keyboard => "
+=head2 keyboard()
+
+Returns a Playwright::Keyboard object.
+
+=cut
+
+sub keyboard {
+    my ( \$self ) = \@_;
+    return Playwright::Keyboard->new(
+        handle => \$self,
+        parent => \$self,
+        id     => \$self->{guid},
+    );
+}\n\n",
+);
+
+our %methods_to_rename = (
+    '$'                 => 'select',
+    '$$'                => 'selectMulti',
+    '$eval'             => 'eval',
+    '$$eval'            => 'evalMulti',
+);
+
+our %bogus_methods = (
+    'querySelector'     => '$',
+    'querySelectorAll'  => '$$',
+    'evalOnSelector'    => '$eval',
+    'evalOnSelectorAll' => '$$eval',
+);
+
+my @modules;
+foreach my $class ( keys(%$spec), 'Mouse', 'Keyboard' ) {
+    next if $class eq 'Playwright';
+    my $pkg = "Playwright::$class";
+    my $subs = '';
+    push(@modules,$pkg);
+    my @seen;
+
+    my $members = Playwright::Util::arr2hash($spec->{$class}{members},'name');
+    foreach my $method ( ( keys( %$members ), 'on', 'evaluate', 'evaluateHandle' ) ) {
+        my $renamed = $method;
+        $method  = $bogus_methods{$method}     if exists $bogus_methods{$method};
+        $renamed = $methods_to_rename{$method} if exists $methods_to_rename{$method};
+        next if grep { $method eq $_ } @seen;
+        if (exists $mapper{$method}) {
+            $subs .= $mapper{$method};
+        } else {
+            $subs .= "
+=head2 $renamed\(\@args)
+
+Execute the $class\:\:$renamed playwright routine.
+
+See L<https://playwright.dev/api/class-$class#$class-$method> for more information.
+
+=cut
+
+sub $renamed {
+    my \$self = shift;
+    return \$self->_request(
+        args    => [\@_],
+        command => '$method',
+        object  => \$self->{guid},
+        type    => \$self->{type}
+    );
+}\n\n";
+        }
+        push(@seen,$method);
+    }
+    my $local_source = $module_source;
+    $local_source =~ s/\%REPLACEME\%/$pkg/gm;
+    $local_source =~ s/\%CLASSNAME\%/$class/gm;
+    $local_source =~ s/\%SUBROUTINES\%/$subs/gm;
+    open(my $fh, '>', "$FindBin::Bin/lib/Playwright/$class.pm");
+    print $fh $local_source;
+    close $fh;
+}
+
+# Now overwrite the list of modules in Playwright.pm
+open(my $fh, '>', "$FindBin::Bin/lib/Playwright/ModuleList.pm");
+print $fh "#ABSTRACT: Playwright sub classes.
+#PODNAME: Playwright::ModuleList
+# You should not use this directly; use Playwright instead.
+
+package Playwright::ModuleList;
+
+use strict;
+use warnings;
+
+";
+
+foreach my $mod (@modules) {
+    print $fh "use $mod;\n";
+}
+
+print $fh "\n1;\n";
+close($fh);
+
+1;
+
+__DATA__
+# ABSTRACT: Automatically generated class for %REPLACEME%
+# PODNAME: %REPLACEME%
+
+# These classes used to be generated at runtime, but are now generated when the module is built.
+# Don't send patches against these modules, they will be ignored.
+# See generate_perl_modules.pl in the repository for generating this.
+
+use strict;
+use warnings;
+
+package %REPLACEME%;
+
+use parent 'Playwright::Base';
+
+=head1 CONSTRUCTOR
+
+=head2 new(%options)
+
+You shouldn't have to call this directly.
+Instead it should be returned to you as the result of calls on Playwright objects, or objects it returns.
+
+=cut
+
+sub new {
+    my ($self,%options) = @_;
+    $options{type} = '%CLASSNAME%';
+    return $self->SUPER::new(%options);
+}
+
+=head1 METHODS
+
+=cut
+
+%SUBROUTINES%
+
+1;

+ 15 - 91
lib/Playwright.pm

@@ -21,6 +21,9 @@ use Carp qw{confess};
 use Playwright::Base();
 use Playwright::Util();
 
+# Stuff closet full of skeletons at BEGIN time
+use Playwright::ModuleList();
+
 no warnings 'experimental';
 use feature qw{signatures};
 
@@ -81,7 +84,7 @@ To fix this, you will need to update your NODE_PATH environment variable to poin
 Feel free to join the Playwright slack server, as there is a dedicated #playwright-perl channel which I, the module author, await your requests in.
 L<https://aka.ms/playwright-slack>
 
-=head3 Why this documentation does not list all available subclasses and their methods
+=head3 Documentation for Playwright Subclasses
 
 The documentation and names for the subclasses of Playwright follow the spec strictly:
 
@@ -89,9 +92,8 @@ Playwright::BrowserContext => L<https://playwright.dev/docs/api/class-browsercon
 Playwright::Page           => L<https://playwright.dev/docs/api/class-page>
 Playwright::ElementHandle  => L<https://playwright.dev/docs/api/class-elementhandle>
 
-...And so on.  100% of the spec is accessible regardless of the Playwright version installed
-due to these classes & their methods being built dynamically at run time based on the specification
-which is shipped with Playwright itself.
+...And so on.  These classes are automatically generated during module build based on the spec hash built by playwright.
+See generate_api_json.sh and generate_perl_modules.pl if you are interested in how this sausage is made.
 
 You can check what methods are installed for each subclass by doing the following:
 
@@ -140,6 +142,9 @@ L<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/ar
 You can also pass Playwright::ElementHandle objects as returned by the select() and selectMulti() routines.
 They will be correctly translated into DOMNodes as you would get from the querySelector() javascript functions.
 
+Calling evaluate() and evaluateHandle() on Playwright::Element objects will automatically pass the DOMNode as the first argument to your script.
+See below for an example of doing this.
+
 =head3 example of evaluate()
 
     # Read the console
@@ -153,10 +158,14 @@ They will be correctly translated into DOMNodes as you would get from the queryS
 
     print "Logged to console: '".$console_log->text()."'\n";
 
+    # Convenient usage of evaluate on ElementHandles
+    # We pass the element itself as the first argument to the JS arguments array for you
+    $element->evaluate('arguments[0].style.backgroundColor = "#FF0000"; return 1;');
+
 
 =head2 Asynchronous operations
 
-The waitFor* methods defined on various classes are essentially a light wrapper around Mojo::IOLoop::Subprocess.
+The waitFor* methods defined on various classes fork and exec, waiting on the promise to complete.
 You will need to wait on the result of the backgrounded action with the await() method documented below.
 
     # Assuming $handle is a Playwright object
@@ -203,7 +212,7 @@ Creates a new browser and returns a handle to interact with it.
 
 =cut
 
-our ( $spec, $server_bin, $node_bin, %mapper, %methods_to_rename );
+our ( $spec, $server_bin, $node_bin, %mapper );
 
 sub _check_node {
 
@@ -247,30 +256,6 @@ sub _check_node {
 }
 
 sub _build_classes {
-    $mapper{mouse} = sub {
-        my ( $self, $res ) = @_;
-        return Playwright::Mouse->new(
-            handle => $self,
-            id     => $res->{_guid},
-            type   => 'Mouse'
-        );
-    };
-    $mapper{keyboard} = sub {
-        my ( $self, $res ) = @_;
-        return Playwright::Keyboard->new(
-            handle => $self,
-            id     => $res->{_guid},
-            type   => 'Keyboard'
-        );
-    };
-
-    %methods_to_rename = (
-        '$'      => 'select',
-        '$$'     => 'selectMulti',
-        '$eval'  => 'eval',
-        '$$eval' => 'evalMulti',
-    );
-
     foreach my $class ( keys(%$spec) ) {
         $mapper{$class} = sub {
             my ( $self, $res ) = @_;
@@ -282,67 +267,6 @@ sub _build_classes {
                 parent => $self,
             );
         };
-
-        #All of the Playwright::* Classes are made by this MAGIC
-        Sub::Install::install_sub(
-            {
-                code => sub ( $classname, %options ) {
-                    @class::ISA = qw{Playwright::Base};
-                    $options{type} = $class;
-                    return Playwright::Base::new( $classname, %options );
-                },
-                as   => 'new',
-                into => "Playwright::$class",
-            }
-        ) unless "Playwright::$class"->can('new');;
-
-        # Hack in mouse and keyboard objects for the Page class
-        if ( $class eq 'Page' ) {
-            foreach my $hid (qw{keyboard mouse}) {
-                Sub::Install::install_sub(
-                    {
-                        code => sub {
-                            my $self = shift;
-                            $Playwright::mapper{$hid}->(
-                                $self,
-                                {
-                                    _type => $self->{type},
-                                    _guid => $self->{guid}
-                                }
-                            ) if exists $Playwright::mapper{$hid};
-                        },
-                        as   => $hid,
-                        into => "Playwright::$class",
-                    }
-                ) unless "Playwright::$class"->can($hid);
-            }
-        }
-
-        # Install the subroutines if they aren't already
-        foreach my $method ( ( keys( %{ $spec->{$class}{members} } ), 'on' ) ) {
-            next if grep { $_ eq $method } qw{keyboard mouse};
-            my $renamed =
-              exists $methods_to_rename{$method}
-              ? $methods_to_rename{$method}
-              : $method;
-
-            Sub::Install::install_sub(
-                {
-                    code => sub {
-                        my $self = shift;
-                        Playwright::Base::_request(
-                            $self,
-                            args    => [@_],
-                            command => $method,
-                            object  => $self->{guid},
-                            type    => $self->{type}
-                        );
-                    },
-                    as   => $renamed,
-                    into => "Playwright::$class",
-                }
-            ) unless "Playwright::$class"->can($renamed);
-        }
     }
 }
 

+ 0 - 1
perlcriticrc

@@ -1 +0,0 @@
-exclude = RequireUseStrict|RequireUseWarnings|ProhibitSubroutinePrototypes

+ 1 - 0
perlcriticrc

@@ -0,0 +1 @@
+conf/perlcriticrc

+ 234 - 0
playwright_server

@@ -0,0 +1,234 @@
+#!/usr/bin/node
+
+"use strict";
+
+// If we don't have this, we're done for
+const { exit } = require('process');
+const fs = require('fs');
+const path = require('path');
+
+// Assume their kit is good
+require('uuid');
+require('playwright');
+require('express');
+
+// Get what we actually want from our deps
+const { v4 : uuidv4 } = require('uuid');
+const { chromium, firefox, webkit, devices } = require('playwright');
+const express = require('express');
+
+// Defines our interface
+// let sharedir = require.resolve('playwright'); // api.json should be shipped with playwright itself
+// var theFile = path.dirname(sharedir) + '/api.json';
+// let rawdata = fs.readFileSync(theFile);
+
+// This is automatically inserted via sed
+let spec =%REPLACEME%
+
+function arr2hash (arr,primary_key) {
+    var inside_out = {};
+    for (var item of arr) {
+        inside_out[item.name] = item;
+    }
+    return inside_out;
+}
+
+var fix_it=false;
+if (spec instanceof Array) {
+    fix_it = true;
+    spec = arr2hash(spec,'name');
+}
+
+// Establish argument order for callers, and correct spec array-ification
+for (var classname of Object.keys(spec)) {
+    if (spec[classname].members instanceof Array) {
+        spec[classname].members = arr2hash(spec[classname].members,'name');
+    }
+    for (var method of Object.keys(spec[classname].members)) {
+        var order = 0;
+        if (spec[classname].members[method].args instanceof Array) {
+            spec[classname].members[method].args = arr2hash(spec[classname].members[method].args,'name');
+        }
+        for (var arg of Object.keys(spec[classname].members[method].args) ) {
+            spec[classname].members[method].args[arg].order = order++;
+        }
+    }
+}
+
+//XXX spec is wrong here unfortunately
+if (fix_it) {
+    for (var className of ['Page','Frame','ElementHandle']) {
+        spec[className].members.$$     = spec[className].members.querySelectorAll;
+        spec[className].members.$      = spec[className].members.querySelector;
+        spec[className].members.$$eval = spec[className].members.evalOnSelectorAll;
+        spec[className].members.$eval  = spec[className].members.evalOnSelector;
+    }
+}
+
+// Parse arguments
+var args = process.argv.slice(2);
+
+if ( args.filter(arg => arg == '--help' || arg == '-h' || arg == '-?' ).length > 0 ) {
+    console.log("Usage:\nplaywright_server [--debug | --check | --port PORT | --help]");
+    exit(0);
+}
+
+if ( args.filter(arg => arg == '--check').length > 0 ) {
+    console.log('OK');
+    exit(0);
+}
+
+var debug = false;
+if ( args.filter(arg => arg == '--debug').length > 0 ) {
+    debug = true;
+}
+
+var got_port = 6969;
+if ( args.filter(arg => arg == '--port').length > 0 ) {
+    var pos = args.indexOf('--port') + 1;
+    if (pos != 0) {
+        got_port = args[pos];
+    }
+}
+
+const app = express();
+const port = got_port;
+
+var objects = {};
+var browsers = { 'firefox' : firefox, 'chrome' : chromium, 'webkit' : webkit };
+
+//Stash for users to put data in
+var userdata = {};
+
+app.use(express.json())
+
+app.get('/spec', async (req, res) => {
+    res.json( { error : false, message : spec } );
+});
+
+app.post('/session', async (req, res) => {
+    var payload = req.body;
+    var type    = payload.type;
+    var args    = payload.args || [];
+
+    if (debug) {
+        console.log("Got launch arguments:");
+        console.log(args);
+    }
+
+    var result;
+    if ( type && browsers[type] ) {
+        try {
+            var browser = await browsers[type].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(result);
+});
+
+app.post('/command', async (req, res) => {
+
+    var payload = req.body;
+    var type    = payload.type;
+    var object  = payload.object;
+    var command = payload.command;
+    var args    = payload.args || [];
+    var result = {};
+
+    if (debug) {
+        console.log(type,object,command,args);
+    }
+
+    // XXX this would be cleaner if the mouse() and keyboard() methods just returned a Mouse and Keyboard object
+    var subject = objects[object];
+    if (subject) {
+        if (type == 'Mouse') {
+            subject = objects[object].mouse;
+        } else if (type == 'Keyboard' ) {
+            subject = objects[object].keyboard;
+        }
+    }
+
+    if (subject && spec[type] && spec[type].members[command]) {
+        try {
+
+            //XXX We have to do a bit of 'special' handling for scripts
+            // This has implications for the type of scripts you can use
+            // In addition, we translate anything with a guid (previously found elements)
+            if (command == 'evaluate' || command == 'evaluateHandle') {
+                var toEval = args.shift();
+                const fun = new Function (toEval);
+                args = args.map( x =>
+                    x.uuid ? objects[x.uuid] : x
+                );
+                args = [
+                    fun,
+                    ...args
+                ];
+            }
+            const res = await subject[command](...args);
+            result = { error : false, message : res };
+
+            if (Array.isArray(res)) {
+                for (var r of res) {
+                    objects[r._guid] = r;
+                }
+            }
+
+            // XXX videos are special, we have to magic up a guid etc for them
+            if (command == 'video' && res) {
+                res._guid = 'Video@' + uuidv4();
+                res._type = 'Video';
+            }
+            // XXX So are FileChooser object unfortunately
+            if (args[0] == 'filechooser' && res) {
+                res._guid = 'FileChooser@' + uuidv4();
+                res._type = 'FileChooser';
+            }
+            // XXX Downloads too sigh
+            if (command == 'waitForEvent' && res._artifact) {
+                res._guid = 'Download@' + uuidv4();
+                res._type = 'Download';
+            }
+
+            if (res && res._guid) {
+                objects[res._guid] = res;
+            }
+        } catch (e) {
+            result = { error : true, message : e.message };
+        }
+    // Allow creation of event listeners if we can actually wait for them
+    } else if (command == 'on' && subject && spec[type].members.waitForEvent ) {
+        try {
+            var evt = args.shift();
+            const cb  = new Function (args.shift());
+            subject.on(evt,cb);
+            result = { error : false, message : "Listener set up" };
+        } catch (e) {
+            result = { error : true, message : e.message };
+        }
+    } else {
+        result = { error : true, message : "No such object, or " + command + " is not a globally recognized command for Playwright" };
+    }
+
+    res.json(result);
+});
+
+app.get('/shutdown', async (req, res) => {
+    res.json( { error: false, message : "Sent kill signal to browser" });
+    process.exit(0);
+});
+
+//Modulino
+if (require.main === module) {
+    app.listen( port, () => {
+        if (debug) {
+            console.log(`Listening on port ${port}`);
+        }
+    });
+}

+ 1 - 32
t/Playwright.t

@@ -12,6 +12,7 @@ use Test::Mock::Cmd qx => sub { $? = $qxcode; return $qxret }, system => sub { p
 no warnings qw{redefine once};
 $Playwright::SKIP_BEGIN = 1;
 use warnings;
+
 require Playwright;
 
 my $path2here = File::Basename::dirname(Cwd::abs_path($INC{'Playwright.pm'}));
@@ -28,38 +29,6 @@ subtest "_check_and_build_spec" => sub {
     like(exception { Playwright::_check_and_build_spec({ ua => 'eeep', port => 666} ) },qr/Could not retrieve/,"Fetch explodes when playwright_server doesn't have spec");
 };
 
-subtest "_build_classes" => sub {
-    local $Playwright::spec = {
-        Fake => {
-            members => {
-                tickle => {
-                    args => {
-                        chase => { type => { name => 'boolean' }, order => 1 },
-                        tickleOptions => {
-                            order => 0,
-                            type => {
-                                name => 'Object',
-                                properties => {
-                                    intense  => { name => 'intense',  type => { name => 'boolean' }  },
-                                    tickler  => { name => 'tickler',  type => { name => 'string'  }  },
-                                    optional => { name => 'optional', type => { name => 'boolean' }  }, # Optional, shouldn't show up in output
-                                },
-                            },
-                        },
-                        who => { type => { name => 'string' },  order => 2 },
-                        hug => { type => { name => 'boolean' }, order => 3 }, # Optional bool arg, make sure we dont choke
-                    },
-                },
-            }
-        },
-    };
-
-    #Very light testing here, example.pl is really what tests this
-    Playwright::_build_classes();
-    ok(defined &Playwright::Fake::new, "Constructors set up correctly");
-    ok(defined &Playwright::Fake::tickle, "Class methods set up correctly");
-};
-
 subtest "_check_node" => sub {
     my $which = Test::MockModule->new('File::Which');
 

Some files were not shown because too many files changed in this diff