playwright_server 9.5 KB

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