Skip to content

Commit 4a0968a

Browse files
Merge pull request #393 from AutomationSolutionz/recorder
Recorder
2 parents 80e9f8e + 882e181 commit 4a0968a

File tree

6 files changed

+277
-9
lines changed

6 files changed

+277
-9
lines changed

Apps/Web/AI_Recorder/background/back_reocrder.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const browserAppData = chrome || browser;
1111
var idx = 0;
1212
var recorded_actions = [];
1313
var action_name_convert = {
14+
select: "click",
1415
type: "text",
1516
open: "go to link",
1617
Go_to_link: "go to link",
@@ -59,6 +60,7 @@ async function fetchAIData(idx, command, value, url, document){
5960
browserAppData.storage.local.set({
6061
recorded_actions: recorded_actions,
6162
})
63+
if (['select', 'click'].includes(command)) value = ""
6264
let validate_full_text_by_ai = false
6365
if (command === 'validate full text by ai'){
6466
command = 'validate full text';
@@ -72,6 +74,7 @@ async function fetchAIData(idx, command, value, url, document){
7274
"action_value": value,
7375
"source": "web",
7476
};
77+
console.log(document);
7578
var data = JSON.stringify(dataj);
7679

7780
const url_ = `${metaData.url}/ai_record_single_action/`

Apps/Web/AI_Recorder/content/loc_builders.js

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,4 +480,236 @@ LocatorBuilders.add('xpath:idRelative', function(e) {
480480
current = current.parentNode;
481481
}
482482
return null;
483-
});
483+
});
484+
485+
// The following code is fetched from https://github.com/firebug/firebug which is BSD licensed
486+
// A 3rd party tool to build exact xpath /html/body/div/...
487+
// Have not implemented this into locator_builder yet
488+
var Xpath = {};
489+
Xpath.getElementXPath = function(element)
490+
{
491+
if (element && element.id)
492+
return '//*[@id="' + element.id + '"]';
493+
else
494+
return Xpath.getElementTreeXPath(element);
495+
};
496+
497+
Xpath.getElementTreeXPath = function(element)
498+
{
499+
var paths = [];
500+
501+
// Use nodeName (instead of localName) so namespace prefix is included (if any).
502+
for (; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode)
503+
{
504+
var index = 0;
505+
var hasFollowingSiblings = false;
506+
for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling)
507+
{
508+
// Ignore document type declaration.
509+
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
510+
continue;
511+
512+
if (sibling.nodeName == element.nodeName)
513+
++index;
514+
}
515+
516+
for (var sibling = element.nextSibling; sibling && !hasFollowingSiblings;
517+
sibling = sibling.nextSibling)
518+
{
519+
if (sibling.nodeName == element.nodeName)
520+
hasFollowingSiblings = true;
521+
}
522+
523+
var tagName = (element.prefix ? element.prefix + ":" : "") + element.localName;
524+
var pathIndex = (index || hasFollowingSiblings ? "[" + (index + 1) + "]" : "");
525+
paths.splice(0, 0, tagName + pathIndex);
526+
}
527+
528+
return paths.length ? "/" + paths.join("/") : null;
529+
};
530+
531+
Xpath.cssToXPath = function(rule)
532+
{
533+
var regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i;
534+
var regAttr1 = /^\[([^\]]*)\]/i;
535+
var regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i;
536+
var regPseudo = /^:([a-z_-])+/i;
537+
var regCombinator = /^(\s*[>+\s])?/i;
538+
var regComma = /^\s*,/i;
539+
540+
var index = 1;
541+
var parts = ["//", "*"];
542+
var lastRule = null;
543+
544+
while (rule.length && rule != lastRule)
545+
{
546+
lastRule = rule;
547+
548+
// Trim leading whitespace
549+
rule = Str.trim(rule);
550+
if (!rule.length)
551+
break;
552+
553+
// Match the element identifier
554+
var m = regElement.exec(rule);
555+
if (m)
556+
{
557+
if (!m[1])
558+
{
559+
// XXXjoe Namespace ignored for now
560+
if (m[5])
561+
parts[index] = m[5];
562+
else
563+
parts[index] = m[2];
564+
}
565+
else if (m[1] == '#')
566+
parts.push("[@id='" + m[2] + "']");
567+
else if (m[1] == '.')
568+
parts.push("[contains(concat(' ',normalize-space(@class),' '), ' " + m[2] + " ')]");
569+
570+
rule = rule.substr(m[0].length);
571+
}
572+
573+
// Match attribute selectors
574+
m = regAttr2.exec(rule);
575+
if (m)
576+
{
577+
if (m[2] == "~=")
578+
parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]");
579+
else
580+
parts.push("[@" + m[1] + "='" + m[3] + "']");
581+
582+
rule = rule.substr(m[0].length);
583+
}
584+
else
585+
{
586+
m = regAttr1.exec(rule);
587+
if (m)
588+
{
589+
parts.push("[@" + m[1] + "]");
590+
rule = rule.substr(m[0].length);
591+
}
592+
}
593+
594+
// Skip over pseudo-classes and pseudo-elements, which are of no use to us
595+
m = regPseudo.exec(rule);
596+
while (m)
597+
{
598+
rule = rule.substr(m[0].length);
599+
m = regPseudo.exec(rule);
600+
}
601+
602+
// Match combinators
603+
m = regCombinator.exec(rule);
604+
if (m && m[0].length)
605+
{
606+
if (m[0].indexOf(">") != -1)
607+
parts.push("/");
608+
else if (m[0].indexOf("+") != -1)
609+
parts.push("/following-sibling::");
610+
else
611+
parts.push("//");
612+
613+
index = parts.length;
614+
parts.push("*");
615+
rule = rule.substr(m[0].length);
616+
}
617+
618+
m = regComma.exec(rule);
619+
if (m)
620+
{
621+
parts.push(" | ", "//", "*");
622+
index = parts.length-1;
623+
rule = rule.substr(m[0].length);
624+
}
625+
}
626+
627+
var xpath = parts.join("");
628+
return xpath;
629+
};
630+
631+
Xpath.getElementsBySelector = function(doc, css)
632+
{
633+
var xpath = Xpath.cssToXPath(css);
634+
return Xpath.getElementsByXPath(doc, xpath);
635+
};
636+
637+
Xpath.getElementsByXPath = function(doc, xpath)
638+
{
639+
try
640+
{
641+
return Xpath.evaluateXPath(doc, xpath);
642+
}
643+
catch(ex)
644+
{
645+
return [];
646+
}
647+
};
648+
649+
/**
650+
* Evaluates an XPath expression.
651+
*
652+
* @param {Document} doc
653+
* @param {String} xpath The XPath expression.
654+
* @param {Node} contextNode The context node.
655+
* @param {int} resultType
656+
*
657+
* @returns {*} The result of the XPath expression, depending on resultType :<br> <ul>
658+
* <li>if it is XPathResult.NUMBER_TYPE, then it returns a Number</li>
659+
* <li>if it is XPathResult.STRING_TYPE, then it returns a String</li>
660+
* <li>if it is XPathResult.BOOLEAN_TYPE, then it returns a boolean</li>
661+
* <li>if it is XPathResult.UNORDERED_NODE_ITERATOR_TYPE
662+
* or XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, then it returns an array of nodes</li>
663+
* <li>if it is XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
664+
* or XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, then it returns an array of nodes</li>
665+
* <li>if it is XPathResult.ANY_UNORDERED_NODE_TYPE
666+
* or XPathResult.FIRST_ORDERED_NODE_TYPE, then it returns a single node</li>
667+
* </ul>
668+
*/
669+
Xpath.evaluateXPath = function(doc, xpath, contextNode, resultType)
670+
{
671+
if (contextNode === undefined)
672+
contextNode = doc;
673+
674+
if (resultType === undefined)
675+
resultType = XPathResult.ANY_TYPE;
676+
677+
var result = doc.evaluate(xpath, contextNode, null, resultType, null);
678+
679+
switch (result.resultType)
680+
{
681+
case XPathResult.NUMBER_TYPE:
682+
return result.numberValue;
683+
684+
case XPathResult.STRING_TYPE:
685+
return result.stringValue;
686+
687+
case XPathResult.BOOLEAN_TYPE:
688+
return result.booleanValue;
689+
690+
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
691+
case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
692+
var nodes = [];
693+
for (var item = result.iterateNext(); item; item = result.iterateNext())
694+
nodes.push(item);
695+
return nodes;
696+
697+
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
698+
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
699+
var nodes = [];
700+
for (var i = 0; i < result.snapshotLength; ++i)
701+
nodes.push(result.snapshotItem(i));
702+
return nodes;
703+
704+
case XPathResult.ANY_UNORDERED_NODE_TYPE:
705+
case XPathResult.FIRST_ORDERED_NODE_TYPE:
706+
return result.singleNodeValue;
707+
}
708+
};
709+
710+
Xpath.getRuleMatchingElements = function(rule, doc)
711+
{
712+
var css = rule.selectorText;
713+
var xpath = Xpath.cssToXPath(css);
714+
return Xpath.getElementsByXPath(doc, xpath);
715+
};

Apps/Web/AI_Recorder/content/rec_handlers.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ Recorder.addEventHandler('type', 'change', function(event) {
6060

6161
/* Recorder Click event */
6262
var preventClickTwice = false;
63+
var select_xpath
6364
Recorder.addEventHandler('clickAt', 'click', function(event) {
65+
// console.log('click event', event);
66+
// console.log("event.target", event.target);
67+
var xpaths = this.locatorBuilders.buildAll(event.target);
68+
if ('select' == event.target.nodeName.toLowerCase()) select_xpath = xpaths
69+
// console.log("xpaths", xpaths);
6470
if (event.button == 0 && !preventClick && event.isTrusted) {
6571
if (!preventClickTwice) {
6672
var top = event.pageY,
@@ -71,9 +77,7 @@ Recorder.addEventHandler('clickAt', 'click', function(event) {
7177
left -= element.offsetLeft;
7278
element = element.offsetParent;
7379
} while (element);
74-
var target = event.target;
75-
this.record("click", this.locatorBuilders.buildAll(event.target), '');
76-
var arrayTest = this.locatorBuilders.buildAll(event.target);
80+
this.record("click", xpaths, '');
7781
preventClickTwice = true;
7882
}
7983
setTimeout(function() { preventClickTwice = false; }, 30);
@@ -456,14 +460,37 @@ Recorder.addEventHandler('select', 'focus', function(event) {
456460
}
457461
}, true);
458462

463+
/* on focusout */
464+
var change_event_invoked = false;
465+
Recorder.addEventHandler('select_focusout', 'focusout', function(event) {
466+
// if (event.target.tagName.toLowerCase() == 'select') console.log('select focusout', event);
467+
if (event.target.tagName.toLowerCase() !== 'select') return;
468+
setTimeout(()=>{
469+
if (change_event_invoked) return;
470+
var option = event.target.options[event.target.selectedIndex];
471+
// console.log("event.target", event.target);
472+
var xpaths = this.locatorBuilders.buildAll(event.target);
473+
if (xpaths.length == 0) xpaths = select_xpath;
474+
// console.log("xpaths", xpaths);
475+
this.record("select", xpaths, this.getOptionLocator(option));
476+
// console.log("option from select focusout event", option);
477+
change_event_invoked = false;
478+
}, 250)
479+
}, true);
480+
459481
/* change */
460-
Recorder.addEventHandler('select', 'change', function(event) {
482+
Recorder.addEventHandler('select_change', 'change', function(event) {
461483
if (event.target.tagName) {
462484
var tagName = event.target.tagName.toLowerCase();
463485
if ('select' == tagName) {
486+
console.log("select change event", event);
464487
if (!event.target.multiple) {
465488
var option = event.target.options[event.target.selectedIndex];
466489
this.record("select", this.locatorBuilders.buildAll(event.target), this.getOptionLocator(option));
490+
change_event_invoked = true;
491+
setTimeout(()=>{
492+
change_event_invoked = false;
493+
},500)
467494
} else {
468495
var options = event.target.options;
469496
for (var i = 0; i < options.length; i++) {

Apps/Web/AI_Recorder/content/recorder.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class Recorder {
5454
return frameLocation = "root" + frameLocation;
5555
}
5656

57-
prepare_dom(target, command){
57+
prepare_dom(target, command, value){
5858
for (let each of target) if (each[1] == 'xpath:position') {
5959
var xpath = each[0];
6060
break;
@@ -66,6 +66,11 @@ class Recorder {
6666
var xPathResult = document.evaluate(xpath, html);
6767
if(xPathResult) var main_elem = xPathResult.iterateNext();
6868
else return;
69+
70+
if (main_elem.tagName === 'SELECT' && command === 'select'){
71+
xPathResult = document.evaluate(`./option[normalize-space(text())="${value.substr(6).trim()}"]`, main_elem);
72+
if(xPathResult) main_elem = xPathResult.iterateNext();
73+
}
6974

7075
main_elem.setAttribute('zeuz', 'aiplugin');
7176
console.log(main_elem.hasAttribute('zeuz'), main_elem);
@@ -97,7 +102,7 @@ class Recorder {
97102
}
98103

99104
record(command, target, value, insertBeforeLastCommand, actualFrameLocation) {
100-
const dom = this.prepare_dom(target, command)
105+
const dom = this.prepare_dom(target, command, value)
101106
browserAppData.runtime.sendMessage({
102107
apiName: 'record_action',
103108
command: command,

Apps/Web/AI_Recorder/panel/assets/js/background/recorder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class BackgroundRecorder {
2222
for(let i = 0; i < result.recorded_actions.length; i++){
2323
if (result.recorded_actions[i] === 'empty'){
2424
console.log("Opacity 2222 =================", result.recorded_actions);
25-
$("#record_label").text("Waiting...");
25+
$("#record_label").text("Recording...");
2626
$("#record").attr('disabled', true).css('opacity',0.5);
2727
return;
2828
}

Framework/Built_In_Automation/Shared_Resources/LocateElement.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=Fa
225225
pass
226226
try:
227227
if CommonUtil.debug_status:
228-
CommonUtil.ExecLog(sModuleInfo, f"{generic_driver.execute_script('return document.body.outerHTML;')}", 3)
228+
# CommonUtil.ExecLog(sModuleInfo, f"{generic_driver.execute_script('return document.body.outerHTML;')}", 3)
229+
pass
229230
except:
230231
pass
231232
return "zeuz_failed"

0 commit comments

Comments
 (0)