From a4e1689ad34e608ce69a81f7a4728e99171a9e4e Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 1 Jul 2017 03:31:17 +0800 Subject: [PATCH] working framework for #24 see issue #24 for more details - working websocket integration with chrome (or headless chrome) - concurrent communications using separate php thread - supporting css and xpath selectors - done following casperjs api - exists, getTitle, getCurrentURL, click (simplified) --- src/tagui_header.js | 71 +++++++++++++++++++++++++++++++++++++-------- src/tagui_parse.php | 41 ++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/tagui_header.js b/src/tagui_header.js index 26175e98..2ac97115 100644 --- a/src/tagui_header.js +++ b/src/tagui_header.js @@ -11,7 +11,7 @@ var p7 = casper.cli.raw.get(6); var p8 = casper.cli.raw.get(7); var p9 = casper. var automation_start_time = Date.now(); casper.echo('\nSTART - automation started - ' + Date().toLocaleString()); // initialise default global variables -var quiet_mode = false; var save_text_count = 0; var snap_image_count = 0; var sikuli_count = 0; +var quiet_mode = false; var save_text_count = 0; var snap_image_count = 0; var sikuli_count = 0; var chrome_id = 0; // variable for advance usage of api step var api_config = {method:'GET', header:[], body:{}}; @@ -85,7 +85,7 @@ function sikuli_handshake() {techo('waiting for sikuli'); var ds; if (flow_path.indexOf('/') !== -1) ds = '/'; else ds = '\\'; var fs = require('fs'); fs.write('tagui.sikuli'+ds+'tagui_sikuli.in','','w'); var sikuli_handshake = ''; if (!fs.exists('tagui.sikuli'+ds+'tagui_sikuli.out')) fs.write('tagui.sikuli'+ds+'tagui_sikuli.out','','w'); -do {sikuli_handshake = fs.read('tagui.sikuli'+ds+'tagui_sikuli.out').trim(); sleep(1000);} +do {sleep(1000); sikuli_handshake = fs.read('tagui.sikuli'+ds+'tagui_sikuli.out').trim();} while (sikuli_handshake !== '[0] START'); techo('connected to sikuli');} @@ -94,20 +94,69 @@ function sikuli_step(sikuli_intent) {sikuli_count++; if (sikuli_count == 1) sikuli_handshake(); // handshake on first call var ds; if (flow_path.indexOf('/') !== -1) ds = '/'; else ds = '\\'; var fs = require('fs'); fs.write('tagui.sikuli'+ds+'tagui_sikuli.in','['+sikuli_count.toString()+'] '+sikuli_intent,'w'); -var sikuli_result = ''; do {sikuli_result = fs.read('tagui.sikuli'+ds+'tagui_sikuli.out').trim(); sleep(1000);} +var sikuli_result = ''; do {sleep(1000); sikuli_result = fs.read('tagui.sikuli'+ds+'tagui_sikuli.out').trim();} while (sikuli_result.indexOf('['+sikuli_count.toString()+'] ') == -1); if (sikuli_result.indexOf('SUCCESS') !== -1) return true; else return false;} +if (chrome_id > 0) { // super large if block to load chrome related functions if chrome or headless option is used +chrome_id = 0; // reset chrome_id from 1 back to 0 to prepare for initial call of chrome_step + +// for initialising integration with chrome web browser +function chrome_handshake() {// techo('waiting for chrome'); +var fs = require('fs'); fs.write('tagui_chrome.in','','w'); var chrome_handshake = ''; +if (!fs.exists('tagui_chrome.out')) fs.write('tagui_chrome.out','','w'); +do {sleep(50); chrome_handshake = fs.read('tagui_chrome.out').trim();} +while (chrome_handshake !== '[0] START'); //techo('connected to chrome'); +} + +// send websocket message to chrome browser using chrome debugging protocol +// php helper process tagui_chrome.php running to handle this concurrently +function chrome_step(method,params) {chrome_id++; +if (chrome_id == 1) chrome_handshake(); // handshake on first call +var chrome_intent = JSON.stringify({'id': chrome_id, 'method': method, 'params': params}); +var fs = require('fs'); fs.write('tagui_chrome.in','['+chrome_id.toString()+'] '+chrome_intent,'w'); +var chrome_result = ''; do {sleep(50); chrome_result = fs.read('tagui_chrome.out').trim();} +while (chrome_result.indexOf('['+chrome_id.toString()+'] ') == -1); +return chrome_result.substring(chrome_result.indexOf('] ')+2);} + // chrome object for handling integration with chrome or headless chrome var chrome = new Object(); chrome.mouse = new Object(); // chrome methods as casper methods replacement for chrome integration +chrome.exists = function(selector) { // different handling for xpath and css +if ((selector.toString().length >= 16) && (selector.toString().substr(0,16) == 'xpath selector: ')) +{if (selector.toString().length == 16) selector = ''; else selector = selector.toString().substring(16); +var ws_message = chrome_step('Runtime.evaluate',{expression: 'document.evaluate(\''+selector+'\',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null).snapshotLength'}); +try {var ws_json = JSON.parse(ws_message); if (ws_json.result.result.value > 0) return true; else return false;} +catch(e) {return false;}} +else {var ws_message = chrome_step('Runtime.evaluate',{expression: 'document.querySelector(\''+selector+'\')'}); +try {var ws_json = JSON.parse(ws_message); if (ws_json.result.result.subtype == 'node') return true; else return false;} +catch(e) {return false;}}}; + chrome.click = function(selector) { -}; +if ((selector.toString().length >= 16) && (selector.toString().substr(0,16) == 'xpath selector: ')) +{if (selector.toString().length == 16) selector = ''; else selector = selector.toString().substring(16); +chrome_step('Runtime.evaluate',{expression: 'document.evaluate(\''+selector+'\',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null).snapshotItem(0).click()'});} +else chrome_step('Runtime.evaluate',{expression: 'document.querySelector(\''+selector+'\').click()'});}; chrome.mouse.move = function(selector) { }; +chrome.mouse.click = function(selector) { +}; + +chrome.mouse.doubleclick = function(selector) { +}; + +chrome.mouse.rightclick = function(selector) { +}; + +chrome.mouse.down = function(selector) { +}; + +chrome.mouse.up = function(selector) { +}; + chrome.sendKeys = function(selector,value) { }; @@ -135,21 +184,19 @@ return ''; }; chrome.getTitle = function() { -return ''; -}; +var ws_message = chrome_step('Runtime.evaluate',{expression: 'document.title'}); +try {var ws_json = JSON.parse(ws_message); return ws_json.result.result.value;} catch(e) {return '';}}; chrome.getCurrentUrl = function() { -return ''; -}; - -chrome.exists = function(selector) { -return false; -}; +var ws_message = chrome_step('Runtime.evaluate',{expression: 'document.location.href'}); +try {var ws_json = JSON.parse(ws_message); return ws_json.result.result.value;} catch(e) {return '';}}; chrome.echo = function(value) {casper.echo(value);}; chrome.on = function(value,statement) {casper.on(value,statement);}; +} // end of super large if block to load chrome related functions if chrome or headless option is used + // for live mode simple parsing of tagui steps into js code function tagui_parse(raw_input) {return parse_intent(raw_input);} diff --git a/src/tagui_parse.php b/src/tagui_parse.php index 9792d57a..e7b8bd1a 100644 --- a/src/tagui_parse.php +++ b/src/tagui_parse.php @@ -23,7 +23,7 @@ $repo_count++;} fclose($repo_file); $repo_count-=2;} //-1 for header, -1 for EOF $tagui_web_browser = "this"; // set the web browser to be used base on tagui_web_browser environment variable -if ((getenv('tagui_web_browser')=='headless') or (getenv('tagui_web_browser')=='chrome')) $tagui_web_browser = "chrome"; +if ((getenv('tagui_web_browser')=='headless') or (getenv('tagui_web_browser')=='chrome')) $tagui_web_browser = 'chrome'; $inside_code_block = 0; // track if step or code is inside user-defined code block $inside_while_loop = 0; // track if step is in while loop and avoid async wait @@ -52,8 +52,35 @@ chmod ($script . '.js',0600); if (!$url_provided) echo "ERROR - first line of " . $script . " not URL\n"; // special handling if chrome or headless chrome is used as browser for automation +// replacement of this.method already happens in step intents, this is mostly to handle user inserted casperjs code if ($tagui_web_browser == 'chrome') {$script_content = file_get_contents($script . '.js'); // read generated script +$script_content = str_replace("var chrome_id = 0;","var chrome_id = 1;",$script_content); // websocket message id $script_content = str_replace("casper.exists","chrome.exists",$script_content); // change locator check to chrome +$script_content = str_replace("this.exists","chrome.exists",$script_content); // change this.exists call as well +$script_content = str_replace("casper.click","chrome.click",$script_content); // change click method to chrome +$script_content = str_replace("this.click","chrome.click",$script_content); // change this.click call as well +$script_content = str_replace("casper.mouse","chrome.mouse",$script_content); // change mouse object to chrome +$script_content = str_replace("this.mouse","chrome.mouse",$script_content); // change this.mouse call as well +$script_content = str_replace("casper.sendKeys","chrome.sendKeys",$script_content); // change sendKeys method to chrome +$script_content = str_replace("this.sendKeys","chrome.sendKeys",$script_content); // change this.sendKeys call as well +$script_content = str_replace("casper.selectOptionByValue","chrome.selectOptionByValue",$script_content); // select option +$script_content = str_replace("this.selectOptionByValue","chrome.selectOptionByValue",$script_content); // select option +$script_content = str_replace("casper.fetchText","chrome.fetchText",$script_content); // change fetchText method to chrome +$script_content = str_replace("this.fetchText","chrome.fetchText",$script_content); // change this.fetchText call as well +$script_content = str_replace("casper.capture","chrome.capture",$script_content); // change capture method to chrome +$script_content = str_replace("this.capture","chrome.capture",$script_content); // change this.capture call as well +$script_content = str_replace("casper.captureSelector","chrome.captureSelector",$script_content); // capture selector +$script_content = str_replace("this.captureSelector","chrome.captureSelector",$script_content); // capture selector +$script_content = str_replace("casper.download","chrome.download",$script_content); // change download method to chrome +$script_content = str_replace("this.download","chrome.download",$script_content); // change this.download call as well +$script_content = str_replace("casper.evaluate","chrome.evaluate",$script_content); // change evaluate method to chrome +$script_content = str_replace("this.evaluate","chrome.evaluate",$script_content); // change this.evaluate call as well +$script_content = str_replace("casper.getHTML","chrome.getHTML",$script_content); // change getHTML method to chrome +$script_content = str_replace("this.getHTML","chrome.getHTML",$script_content); // change this.getHTML call as well +$script_content = str_replace("casper.getTitle","chrome.getTitle",$script_content); // change getTitle method to chrome +$script_content = str_replace("this.getTitle","chrome.getTitle",$script_content); // change this.getTitle call as well +$script_content = str_replace("casper.getCurrentUrl","chrome.getCurrentUrl",$script_content); // get current url +$script_content = str_replace("this.getCurrentUrl","chrome.getCurrentUrl",$script_content); // get current url file_put_contents($script . '.js',$script_content);} // check quiet parameter to run flow quietly by only showing explicit output @@ -248,13 +275,15 @@ function call_sikuli($input_intent,$input_params) { // helper function to use si end_fi()."});\n\ncasper.then(function() {\n";} // set of functions to interpret steps into corresponding casperjs code -function url_intent($raw_intent) { +function url_intent($raw_intent) {$twb = $GLOBALS['tagui_web_browser']; $casper_url = $raw_intent; $chrome_call = ''; +if ($twb == 'chrome') +{$casper_url = 'about:blank'; $chrome_call = "chrome_step('Page.navigate',{url: '".$raw_intent."'}); sleep(1000);\n";} if (filter_var($raw_intent, FILTER_VALIDATE_URL) == false) echo "ERROR - " . current_line() . " invalid URL " . $raw_intent . "\n"; else -if ($GLOBALS['line_number'] == 1) {$GLOBALS['url_provided'] = true; return "casper.start('".$raw_intent. -"', function() {\ntecho('".$raw_intent."' + ' - ' + this.getTitle() + '\\n');});\n\ncasper.then(function() {\n";} -else return "});casper.thenOpen('".$raw_intent."', function() {\ntecho('". -$raw_intent."' + ' - ' + this.getTitle());});\n\ncasper.then(function() {\n";} +if ($GLOBALS['line_number'] == 1) {$GLOBALS['url_provided']=true; return "casper.start('".$casper_url."', function() {\n". +$chrome_call."techo('".$raw_intent."' + ' - ' + ".$twb.".getTitle() + '\\n');});\n\ncasper.then(function() {\n";} +else return "});casper.thenOpen('".$casper_url."', function() {\n".$chrome_call."techo('". +$raw_intent."' + ' - ' + ".$twb.".getTitle());});\n\ncasper.then(function() {\n";} function tap_intent($raw_intent) {$twb = $GLOBALS['tagui_web_browser']; $params = trim(substr($raw_intent." ",1+strpos($raw_intent." "," ")));