playwright_server 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 | --cdp URI --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. var cdp_uri = '';
  79. if ( args.filter(arg => arg == '--cdp').length > 0 ) {
  80. var pos = args.indexOf('--cdp') + 1;
  81. if (pos !=0) {
  82. cdp_uri = args[pos];
  83. }
  84. }
  85. const app = express();
  86. const port = got_port;
  87. var objects = {};
  88. var browsers = { 'firefox' : firefox, 'chrome' : chromium, 'webkit' : webkit };
  89. //Stash for users to put data in
  90. var userdata = {};
  91. app.use(express.json())
  92. app.get('/spec', async (req, res) => {
  93. res.json( { error : false, message : spec } );
  94. });
  95. app.post('/session', async (req, res) => {
  96. var payload = req.body;
  97. var type = payload.type;
  98. var args = payload.args || [];
  99. if (debug) {
  100. console.log("Got launch arguments:");
  101. console.log(args);
  102. }
  103. var result;
  104. if ( type && browsers[type] ) {
  105. try {
  106. var browserServer = await browsers[type].launchServer(...args);
  107. var wsEndpoint = browserServer.wsEndpoint();
  108. var browser;
  109. if (cdp_uri == '') {
  110. browser = await browsers[type].connect({ wsEndpoint });
  111. } else {
  112. browser = await browsers[type].connectOverCDP( cdp_uri );
  113. }
  114. browser.server = browserServer;
  115. objects[browser._guid] = browser;
  116. result = { error : false, message : browser };
  117. } catch (e) {
  118. result = { error : true, message : e.message};
  119. }
  120. } else {
  121. result = { error : true, message : "Please select a supported browser" };
  122. }
  123. res.json(result);
  124. });
  125. app.post('/server', async (req, res) => {
  126. var payload = req.body;
  127. var object = payload.object;
  128. var command = payload.command;
  129. var result = { error : true, message : "Please pass a valid browser object ID. got:", object };
  130. if (debug) {
  131. console.log(object,command);
  132. }
  133. if (objects[object]) {
  134. var msg = objects[object].server[command](...args);
  135. result = { error : false, message : msg };
  136. }
  137. res.json(result);
  138. });
  139. app.post('/command', async (req, res) => {
  140. var payload = req.body;
  141. var type = payload.type;
  142. var object = payload.object;
  143. var command = payload.command;
  144. var args = payload.args || [];
  145. var result = {};
  146. if (debug) {
  147. console.log(type,object,command,args);
  148. }
  149. // XXX this would be cleaner if the mouse() and keyboard() methods just returned a Mouse and Keyboard object
  150. var subject = objects[object];
  151. if (subject) {
  152. if (type == 'Mouse') {
  153. subject = objects[object].mouse;
  154. } else if (type == 'Keyboard' ) {
  155. subject = objects[object].keyboard;
  156. }
  157. }
  158. if (subject && spec[type] && spec[type].members[command]) {
  159. try {
  160. //XXX We have to do a bit of 'special' handling for scripts
  161. // This has implications for the type of scripts you can use
  162. // In addition, we translate anything with a guid (previously found elements)
  163. if (command == 'evaluate' || command == 'evaluateHandle') {
  164. var toEval = args.shift();
  165. const fun = new Function (toEval);
  166. args = args.map( x =>
  167. x.uuid ? objects[x.uuid] : x
  168. );
  169. args = [
  170. fun,
  171. ...args
  172. ];
  173. }
  174. var commandResult;
  175. if (command == 'request') {
  176. //TODO extend this to other attribute fetches as well in the future
  177. commandResult = subject[command];
  178. } else {
  179. commandResult = await subject[command](...args);
  180. }
  181. if (Array.isArray(commandResult)) {
  182. for (var r of commandResult) {
  183. objects[r._guid] = r;
  184. }
  185. }
  186. // XXX videos are special, we have to magic up a guid etc for them
  187. if (command == 'video' && commandResult) {
  188. commandResult._guid = 'Video@' + uuidv4();
  189. commandResult._type = 'Video';
  190. }
  191. // XXX So are FileChooser object unfortunately
  192. if (args[0] == 'filechooser' && commandResult) {
  193. commandResult._guid = 'FileChooser@' + uuidv4();
  194. commandResult._type = 'FileChooser';
  195. }
  196. // XXX Downloads too sigh
  197. if (command == 'waitForEvent' && commandResult._artifact) {
  198. commandResult._guid = 'Download@' + uuidv4();
  199. commandResult._type = 'Download';
  200. }
  201. // XXX Console logs too
  202. if (command == 'waitForEvent' && commandResult._event && commandResult._event.type == 'log') {
  203. commandResult._guid = 'ConsoleMessage@' + uuidv4();
  204. commandResult._type = 'ConsoleMessage';
  205. }
  206. // XXX I think you are starting to see a pattern here
  207. if (commandResult && commandResult._initializer && commandResult._initializer.fetchUid) {
  208. commandResult._guid = 'FetchResponse@' + uuidv4();
  209. commandResult._type = 'FetchResponse';
  210. }
  211. if (commandResult && spec[type].members[command].type.name == 'Locator') {
  212. commandResult._guid = 'Locator@' + uuidv4();
  213. commandResult._type = 'Locator';
  214. }
  215. var toReturn = commandResult;
  216. // XXX we have to duplicate this parameter so as not to confuse playwright when we change it to reflect the spec
  217. if (commandResult && commandResult._type) {
  218. toReturn = { _guid : commandResult._guid, _type : commandResult._type };
  219. }
  220. // XXX APIRequestContexts & friends are still internally FetchRequests
  221. if (commandResult && commandResult._type == 'FetchRequest') {
  222. toReturn._type = 'APIRequestContext';
  223. }
  224. if (commandResult && commandResult._type == 'FetchResponse') {
  225. toReturn._type = 'APIResponse';
  226. }
  227. if (commandResult && commandResult._guid) {
  228. objects[commandResult._guid] = commandResult;
  229. }
  230. result = { error : false, message : toReturn };
  231. } catch (e) {
  232. result = { error : true, message : e.message };
  233. }
  234. // Allow creation of event listeners if we can actually wait for them
  235. } else if (command == 'on' && subject && spec[type].members.waitForEvent ) {
  236. try {
  237. var evt = args.shift();
  238. const cb = new Function (args.shift());
  239. subject.on(evt,cb);
  240. result = { error : false, message : "Listener set up" };
  241. } catch (e) {
  242. result = { error : true, message : e.message };
  243. }
  244. } else {
  245. result = { error : true, message : "No such object, or " + command + " is not a globally recognized command for Playwright" };
  246. }
  247. res.json(result);
  248. });
  249. app.get('/shutdown', async (req, res) => {
  250. res.json( { error: false, message : "Sent kill signal to browser" });
  251. process.exit(0);
  252. });
  253. //Modulino
  254. if (require.main === module) {
  255. app.listen( port, () => {
  256. if (debug) {
  257. console.log(`Listening on port ${port}`);
  258. }
  259. });
  260. }