playwright_server 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/node
  2. "use strict";
  3. // If we don't have this, we're done for
  4. const { exit } = require('process');
  5. const fs = require('fs');
  6. const path = require('path');
  7. // Assume their kit is good
  8. require('uuid');
  9. require('playwright');
  10. require('express');
  11. // Get what we actually want from our deps
  12. const { v4 : uuidv4 } = require('uuid');
  13. const { chromium, firefox, webkit, devices } = require('playwright');
  14. const express = require('express');
  15. // Defines our interface
  16. // let sharedir = require.resolve('playwright'); // api.json should be shipped with playwright itself
  17. // var theFile = path.dirname(sharedir) + '/api.json';
  18. // let rawdata = fs.readFileSync(theFile);
  19. // This is automatically inserted via sed
  20. let spec =%REPLACEME%
  21. function arr2hash (arr,primary_key) {
  22. var inside_out = {};
  23. for (var item of arr) {
  24. inside_out[item.name] = item;
  25. }
  26. return inside_out;
  27. }
  28. var fix_it=false;
  29. if (spec instanceof Array) {
  30. fix_it = true;
  31. spec = arr2hash(spec,'name');
  32. }
  33. // Establish argument order for callers, and correct spec array-ification
  34. for (var classname of Object.keys(spec)) {
  35. if (spec[classname].members instanceof Array) {
  36. spec[classname].members = arr2hash(spec[classname].members,'name');
  37. }
  38. for (var method of Object.keys(spec[classname].members)) {
  39. var order = 0;
  40. if (spec[classname].members[method].args instanceof Array) {
  41. spec[classname].members[method].args = arr2hash(spec[classname].members[method].args,'name');
  42. }
  43. for (var arg of Object.keys(spec[classname].members[method].args) ) {
  44. spec[classname].members[method].args[arg].order = order++;
  45. }
  46. }
  47. }
  48. //XXX spec is wrong here unfortunately
  49. if (fix_it) {
  50. for (var className of ['Page','Frame','ElementHandle']) {
  51. spec[className].members.$$ = spec[className].members.querySelectorAll;
  52. spec[className].members.$ = spec[className].members.querySelector;
  53. spec[className].members.$$eval = spec[className].members.evalOnSelectorAll;
  54. spec[className].members.$eval = spec[className].members.evalOnSelector;
  55. }
  56. }
  57. // Parse arguments
  58. var args = process.argv.slice(2);
  59. if ( args.filter(arg => arg == '--help' || arg == '-h' || arg == '-?' ).length > 0 ) {
  60. console.log("Usage:\nplaywright_server [--debug | --check | --port PORT | --help]");
  61. exit(0);
  62. }
  63. if ( args.filter(arg => arg == '--check').length > 0 ) {
  64. console.log('OK');
  65. exit(0);
  66. }
  67. var debug = false;
  68. if ( args.filter(arg => arg == '--debug').length > 0 ) {
  69. debug = true;
  70. }
  71. var got_port = 6969;
  72. if ( args.filter(arg => arg == '--port').length > 0 ) {
  73. var pos = args.indexOf('--port') + 1;
  74. if (pos != 0) {
  75. got_port = args[pos];
  76. }
  77. }
  78. const app = express();
  79. const port = got_port;
  80. var objects = {};
  81. var browsers = { 'firefox' : firefox, 'chrome' : chromium, 'webkit' : webkit };
  82. //Stash for users to put data in
  83. var userdata = {};
  84. app.use(express.json())
  85. app.get('/spec', async (req, res) => {
  86. res.json( { error : false, message : spec } );
  87. });
  88. app.post('/session', async (req, res) => {
  89. var payload = req.body;
  90. var type = payload.type;
  91. var args = payload.args || [];
  92. if (debug) {
  93. console.log("Got launch arguments:");
  94. console.log(args);
  95. }
  96. var result;
  97. if ( type && browsers[type] ) {
  98. try {
  99. var browser = await browsers[type].launch(...args);
  100. objects[browser._guid] = browser;
  101. result = { error : false, message : browser };
  102. } catch (e) {
  103. result = { error : true, message : e.message};
  104. }
  105. } else {
  106. result = { error : true, message : "Please select a supported browser" };
  107. }
  108. res.json(result);
  109. });
  110. app.post('/command', async (req, res) => {
  111. var payload = req.body;
  112. var type = payload.type;
  113. var object = payload.object;
  114. var command = payload.command;
  115. var args = payload.args || [];
  116. var result = {};
  117. if (debug) {
  118. console.log(type,object,command,args);
  119. }
  120. // XXX this would be cleaner if the mouse() and keyboard() methods just returned a Mouse and Keyboard object
  121. var subject = objects[object];
  122. if (subject) {
  123. if (type == 'Mouse') {
  124. subject = objects[object].mouse;
  125. } else if (type == 'Keyboard' ) {
  126. subject = objects[object].keyboard;
  127. }
  128. }
  129. if (subject && spec[type] && spec[type].members[command]) {
  130. try {
  131. //XXX We have to do a bit of 'special' handling for scripts
  132. // This has implications for the type of scripts you can use
  133. // In addition, we translate anything with a guid (previously found elements)
  134. if (command == 'evaluate' || command == 'evaluateHandle') {
  135. var toEval = args.shift();
  136. const fun = new Function (toEval);
  137. args = args.map( x =>
  138. x.uuid ? objects[x.uuid] : x
  139. );
  140. args = [
  141. fun,
  142. ...args
  143. ];
  144. }
  145. const res = await subject[command](...args);
  146. result = { error : false, message : res };
  147. if (Array.isArray(res)) {
  148. for (var r of res) {
  149. objects[r._guid] = r;
  150. }
  151. }
  152. // XXX videos are special, we have to magic up a guid etc for them
  153. if (command == 'video' && res) {
  154. res._guid = 'Video@' + uuidv4();
  155. res._type = 'Video';
  156. }
  157. // XXX So are FileChooser object unfortunately
  158. if (args[0] == 'filechooser' && res) {
  159. res._guid = 'FileChooser@' + uuidv4();
  160. res._type = 'FileChooser';
  161. }
  162. // XXX Downloads too sigh
  163. if (command == 'waitForEvent' && res._artifact) {
  164. res._guid = 'Download@' + uuidv4();
  165. res._type = 'Download';
  166. }
  167. if (res && res._guid) {
  168. objects[res._guid] = res;
  169. }
  170. } catch (e) {
  171. result = { error : true, message : e.message };
  172. }
  173. // Allow creation of event listeners if we can actually wait for them
  174. } else if (command == 'on' && subject && spec[type].members.waitForEvent ) {
  175. try {
  176. var evt = args.shift();
  177. const cb = new Function (args.shift());
  178. subject.on(evt,cb);
  179. result = { error : false, message : "Listener set up" };
  180. } catch (e) {
  181. result = { error : true, message : e.message };
  182. }
  183. } else {
  184. result = { error : true, message : "No such object, or " + command + " is not a globally recognized command for Playwright" };
  185. }
  186. res.json(result);
  187. });
  188. app.get('/shutdown', async (req, res) => {
  189. res.json( { error: false, message : "Sent kill signal to browser" });
  190. process.exit(0);
  191. });
  192. //Modulino
  193. if (require.main === module) {
  194. app.listen( port, () => {
  195. if (debug) {
  196. console.log(`Listening on port ${port}`);
  197. }
  198. });
  199. }