aisingapore / TagUI

Free RPA tool by AI Singapore
Apache License 2.0
5.58k stars 580 forks source link

Issue with local variables (passing to browser DOM context) #55

Closed zjevik closed 7 years ago

zjevik commented 7 years ago

First of all, I love this project and I'm a big fan!

I have a website with TinyMCE text editor and I would like to enter some HTML code in the text field. Unfortunately, when I click on HTML Code View the page will not render. I decided to use javascript and inject the code using a step dom. This works but I cannot access variables I created outside of the dom step.

Is there a way to change the default execution context from local to global? This way I would be able to access all variables in the dom step.

Also, if I create a variable in one dom step then the variable is gone if I try to access it in another dom step.

Thanks, Ondrej

kensoh commented 7 years ago

Hi @zjevik thanks for your feedback!

Yes to modify the actual browser DOM will need dom step. The issue is by design CasperJS operates in a separate local context instead of the actual browser context. dom step translates into the casper.evaluate function and this is the only way to pass variables into browser DOM.

To put variables into the browser DOM, you can see the example from the casper.evaluate link. It explains more and why it would be tough for a program to automatically understand the statements to recognize variables and write a proper casper.evaluate call to pass in the various variables detected.

Below is a modification of the example which you can use to pass in variables and retrieve output. It is combined into 1 line or it will trigger the built-in error checking for step { and }.

dom_result = casper.evaluate(function(username, password) {document.querySelector('#username').value = username; document.querySelector('#password').value = password; document.querySelector('#submit').click(); test_value = 1; return test_value;}, 'sheldon.cooper', 'b4z1ng4');

In other steps, to use variables '+variable_name+' can be used. But for this case it can't, because the variable_name has no meaning once it is being attempted to reference in the browser DOM context.

The variables created within dom steps should be able to be referenced within other dom steps. Just don't use var to define, otherwise the variable will only exist within that call. Following will output 3.

dom a = 1
dom b = 2
dom c = a + b
dom return c
echo dom_result

You may want to raise an issue at CasperJS repo to see if context can be changed. I don't think so, both for security reason for technical design. Otherwise it allows a malicious website to access the variables you created in your script or sensitive data such as your local file contents.

UPDATE - internal case note using a standard variable, eg dom_json = {a:1, b:2}, it is possible to pass variables into browser context with dom step. approach tested ok. but to do that for chrome mode using devtools runtime.evaluate..

chrome.evaluate = function(fn_statement) { // evaluate expression in browser dom context
// chrome runtime.evaluate is different from casperjs evaluate, do some processing to reduce gap
var statement = fn_statement.toString(); statement = statement.slice(statement.indexOf('{')+1,statement.lastIndexOf('}'));
statement = statement.replace(/return /g,''); // defining as function() and return keyword is invalid for chrome
var ws_message = chrome_step('Runtime.evaluate',{expression: statement}); // statements can be separated by ;
try {var ws_json = JSON.parse(ws_message); if (ws_json.result.result.value)
return ws_json.result.result.value; else return '';} catch(e) {return '';}};

UPDATE - internal case note below works, credits and thanks to Shinji (@dotneet) and Cyrus (@cyrus-and)!

chrome.evaluate = function(fn_statement,eval_json) { // evaluate expression in browser dom context
// chrome runtime.evaluate is different from casperjs evaluate, do some processing to reduce gap
var statement = fn_statement.toString(); if (!eval_json)
{statement = statement.slice(statement.indexOf('{')+1,statement.lastIndexOf('}'));
statement = statement.replace(/return /g,'');} // defining function() with return keyword is invalid for chrome
else statement = '(' + statement + ')' + '(' + JSON.stringify(eval_json) + ')'; // unless variable is passed into fx
var ws_message = chrome_step('Runtime.evaluate',{expression: statement}); // statements can be separated by ;
try {var ws_json = JSON.parse(ws_message); if (ws_json.result.result.value)
return ws_json.result.result.value; else return null;} catch(e) {return null;}};
kensoh commented 7 years ago

Commited to master with following comments. By unifying to a standard JSON object, consistent translations can be made from TagUI flow language into matching casper.evaluate or chrome.evaluate.

UPDATE - below a clearer example of passing and accessing variables in action

// this will output 10 as dom_json is now accessible in browser DOM context
a = 1; b = 2; c = 3; d = 4;
dom_json = {a: a, b: b, c: c, d: d}
dom return dom_json.a + dom_json.b + dom_json.c + dom_json.d
echo dom_result

(outstanding update readme when releasing packaged installations on next release)

zjevik commented 7 years ago

Perfect! dom_json works perfectly!

zjevik commented 7 years ago

@kensoh One more thing I found during testing dom_json. The following code is not going to work:

aa = 10; dom_json = {aa: aa} dom return dom_json.aa echo dom_result

dom_json will not work if only one variable is passed.

kensoh commented 7 years ago

Nice catch! I traced the code, it looks like CasperJS and Chrome will cast the JSON object into a single normal variable if the object has only 1 element. In that case for 1 element, following works -

// example for passing a single variable value
aa = 10;

// use either this
dom_json = {aa: aa}

// or assign directly
dom_json = aa

// access dom_json directly
dom return dom_json
echo dom_result

As this is probably JavaScript behavior, a note should be put when the readme / sample flow is updated in next release. (Alternatively, adding a dummy element before every call should work. But I prefer the simplicity for dom_json = variable and using it directly for single variable scenarios.)

zjevik commented 7 years ago

Interesting! I'm glad I helped.