ContributionsMost RecentMost LikesSolutionsAdvanced search object for complex model Hello all, Please find here my implementation of advanced search of in-memory object for complex tree model. Sometimes when the tested app is really complex (especially in heavy client application), the search for object could be too slow due to depth. One of the solution is to do by finding intermediate object to narrow and guide the search. So you multiply findChildEx. To ease that i made the following method : /** * <a id="system.findContainer"></a> * Rechercher un objet TestComplete en mémoire de manière containérisé<br> * Si <i>system.debug</i> est à <b>true</b> alors des logs complémentaires sur la recherche sont inscrits * @memberof system * @function * {Object} ParentObject - Objet parent porteur de l'objet à rechercher. Racine de départ de la recherche. Plus l'objet parent est de haut niveau, plus le temps de recherche peut être long * {array} Props - Tableau des propriétés des objets à rechercher par container, si plusieurs propriétés recherchées pour un container donnée alors le séparateur est le |, exemple ["Visible|Name", "Name"] -> recherche des 2 propriétés Visble et Name pour le premier container et recherche de Name seulement pour le deuxième container * {array} Values - Tableau des valeurs des propriétés des objets à rechercher * {number|array} [Depth=4] - Limiter la recherche a <i>Depth>/i> profondeur depuis l'objet parent. Plus la profondeur est grande, plus le temps de recherche peut être long. Peut être un tableau pour définir un temps de rechercher par container * @returns {object} Renvoie l'objet s'il existe ou <b>null</b> le cas échéant ou bien en cas d'erreur */ system.findContainer = function(ParentObject = null, Props = null, Values = null, Depth = 4) { // Vérification des paramètres obligatoires if ((ParentObject == null) || (Props == null) || (Values == null)) { if (system.debug) Log.Message('findContainer() - Un paramètre obligatoire est non renseigné (ParentObject ou Props ou Values)', "", pmHigher, system.logWarning); return null; } if (ParentObject == "current") { if ((typeof system.container != 'undefined') && (system.container != null) && (system.container.Exists)) ParentObject = system.container else return null; } if (system.debug) { let propsKey = typeof Props == "string" ? Props : Props.join("|"); let valuesKey; switch (typeof Values) { case "string": valuesKey = Values; break; case "number": case "boolean": valuesKey = Values.toString(); break; case "null": valuesKey = "null"; break; default: valuesKey = Values.join("|"); break; } Log.Message("findContainer(" + ParentObject.FullName + ", " + propsKey + ", " + valuesKey + ", " + Depth.toString() + ") - Recherche d'objet", "", pmLowest, system.logDebug); } var objectfind = ParentObject; let currentProps; let currentValues; let currentDepth; try { for (let i=0;i<Props.length;i++) { currentProps = Props[i].split('|'); currentValues = new Array(); currentDepth = typeof Depth == 'number' ? Depth : Depth[i]; for (let j=0;j<currentProps.length;j++) { currentValues.push(Values.shift()); } objectfind = objectfind.FindChildEx(currentProps, currentValues, currentDepth, true, system.time.medium); if ((typeof objectfind == 'undefined') || ((objectfind != null) && (!objectfind.Exists))) break; } } catch (e) { objectfind = null; if (system.debug) Log.Message("findContainer() - Une exception est apparue dans la recherche", e.message, pmHighest, system.logError); } finally { // Ne renvoyer que "null" sur non trouvé ou erreur if ((typeof objectfind == 'undefined') || ((objectfind != null) && (!objectfind.Exists))) objectfind = null; if (system.debug) { if (objectfind == null) Log.Message("findContainer() renvoie un objet null ou undefined", "", pmLowest, system.logDebug) else Log.Message('findContainer() a trouvé un objet', objectfind.FullName, pmLowest, system.logDebug); } return objectfind; } } It comes from my testing framework so everything starting by system. is specific but you should understand the use : system.debug -> true to activate an additionnal level of log. system.logDebug -> specific debug log attributes. system.container -> a global variable that can hold a frequently used object in portion of test system.time.medium -> this is 5 seconds (5000ms) Samples usage : let objectToTest = system.findContainer(SourceObject, ["Visible|Name", "Visible|Name"], [true,'dlmAccueil', true,'editionEnfants']); Will search in SourceObject a visible object named 'editionEnfants' which is located inside another visible object named 'dlmAccueil'. The search will use a default max depth of 4 levels for both objects. let objectToTest = system.findContainer(SourceObject, ["Visible|Name", "Name", "Header|Index|Visible"], [true, "Saisie", "DockingPanel"', "ILPaneGroup", 1, true], [2, 3, 2]); Will search in SourceObject a visible object with property Index to 1 and property Header to 'ILPanelGroup' which is located inside another object named 'DockingPanel' which is located inside another visible object named 'Saisie'. The search will use a depth of 2 levels for first object, 3 levels for second object and 2 levels for final object. Re: Comparing 2 numbers contained in different web objects Convert both values in numbers with regEx. See here (look for Getting numerical values from string) function ExtractNumber(Str, DefaultValue) { var MatchArr, re; re = /[-+]?\d*\.?\d+([eE][-+]?\d+)?/gm; //Specify the regular expression MatchArr=Str.match(re); //Search for occurrences //If no numbers were found then return default value if (MatchArr==null) return DefaultValue //Else, convert a string with first occurrence into a number else return aqConvert.StrToFloat(MatchArr[0]); } So you'll have something like that : let value1 = ExtractNumber(WebObject1.Property, -1); let value2 = ExtractNumber(WebObject2.Property, -1); let value1IsGreaterThanValue2 = value1 > value2; Re: Upcoming TestComplete Training Courses! You can also contact me to have personal courses at interesting prices with high ROI in French, English, Spanish, Polynesian and Martian languages ;^) Disclaimer : Martian language is in beta phase, please apologises for low carbon level speaking usage. Re: Best practices for automation Both. Depends on object tested, on test strategy, on test budget, on client, on your skills, ... In fact, i build business test object starting from small/single action (like callMenu, closeCurrentScreen, selectValueInList, callToolbarAction, ...) and slowly aggregating these to build small business action (like selectSample, managePrinter, manageUserRigths, ..) to finally ending with complex business action (like editSample, editProject). I keep the same name of small/single action or small business action whatever the tested app is. After that i build test case using theses actions as test brick. At the end i've this kind of test : /** * @file Bibliothèque TestComplete des cas de test Ariane : Test item 22696 * @author Biache Benoit * @version 1.00 */ //USEUNIT ariane //USEUNIT arianeEngine //USEUNIT POC_Globals //USEUNIT POC_Context /*****************************************************************************************/ /***************************** Implémentation test item 22696 ****************************/ /*****************************************************************************************/ /** * Test fonctionel CT 22696 Saisie demande Anapath [Cas 1] * @function * @returns {boolean} Renvoie <b>true</b> si le test s'est bien effectué */ function CT_22696() { if (!startTest("22696", "Saisie demande Anapath [Cas 1]")) return false; let go = true; try { /* Début CT -------------------------------------------------------------------------*/ let data = globals.currentTestData; go = go ? myAriane.connect (data.LOGIN, data.PASSWORD, data.VERSION) : false; go = go ? myAriane.callRibbonMenu (ariane.ribbonMenu.demandeAnapath) : false; go = go ? myAriane.selectPrinterModels ("Feuille demande=" + data.IMP_FEUILDEMANDE + ";Étiquette prélèvement=" + data.IMP_ETIQPRELEV) : false; go = go ? myAriane.selectHospital (data.CODE_ETB, data.NOM_ETB) : false; go = go ? myAriane.selectCriteria ("**bleep**|Egal à|T=" + globals.currentTestData.patient.**bleep**) : false; go = go ? myAriane.setSimplifiedRequest ("Urgent=true;Prescripteur=" + data.PRESCRIPTEUR + "|" + data.ADR_PRESCRIPTEUR + ";Destinataire=" + data.DESTINATAIRE + "|" + data.ADR_DESTINATAIRE, ariane.statusMenu.none) : false; go = go ? myAriane.setSimplifiedSample ("Date=" + data.DATEPRELEV + ";Nombre=" + data.NBPRELEV + ";Définition=" + data.AXE1 + "|" + data.AXE2 + "|" + data.AXE3 + "|" + data.AXE4, ariane.statusMenu.none) : false; go = go ? myAriane.editSample (2, "Commentaire=Test du commentaire^a~u|TEST DU COMMENTAIRE", ariane.statusMenu.saveClose) : false; go = go ? myAriane.closeCurrentScreen () : false; if (!go) { myAriane.closeCurrentScreen(); throw Error(myAriane.getErrorString()); } /* Fin CT ---------------------------------------------------------------------------*/ } catch(e) { globals.error = e.message; go = false; } go ? endTest("SUCCESS") : endTest("FAILURE"); return go; } Re: Disable warning about text being truncated I understand that you want to keep things in KISS principle and i was like you until a day i lost several hours because an error occured inside Log.Enabled off section .. :^) So ideally you should use a try .. finally pattern for the log off/on. Advantages of event method are : - Event exists for that kind of action - It keep things centralized (reduce cost of maintenability) - It allows you to add other stuff like making choice if real log made will be warning or message or even error or like avoiding multiple entries of same log or adding additionnal informations or ... Yes this is heavier but once .. Re: Disable warning about text being truncated Use the onLogWarning event. 2 ways, check the Sender object to filter on source basis or check the LogParams to filter on message basis. Below a sample of message basis filtering : var globals = {}; globals.WARNINGTOSKIP = ['The text cannot be fully entered', 'The length is too long', 'Another message to skip']; // Array of the warnings message to skip globals.IGNOREWARNING = false; // True then convert Log.Warning into Log.Message function GeneralEvents_OnLogWarning(Sender, LogParams) { // Detect if, based on log message, it's a warning to be totally ignored let skip = false; for (let i = 0; i < globals.WARNINGTOSKIP.length; i++) { skip = skip == false ? aqObject.CompareProperty(LogParams.MessageText, cmpContains, globals.WARNINGTOSKIP[i], false, lmNone) : true; } if (skip) { Log.Message(LogParams.MessageText); LogParams.Locked = true; } else { // If just message instead of warning if (globals.IGNOREWARNING) { Log.Message(LogParams.MessageText, LogParams.AdditionalText); LogParams.Locked = true; } else { // Need to switch to avoid reentrancy let f = GeneralEvents_OnLogWarning; GeneralEvents_OnLogWarning = null; Log.Warning(LogParams.MessageText, LogParams.AdditionalText); GeneralEvents_OnLogWarning = f; LogParams.Locked = true; } } } Re: Device Keys not working properly Is it always the beginning of inserted data that is mising ? or could be also end of data ? If it's always the beginning, perhaps the field need to be focused before text insertion is done ? One base app like Message on your mobile, does the behaviour is the same ? (e.g. find if it's due to your tested app or your test technique ?). Re: How to create Frame work and How to call all functions in one main? 1.1 How to design a framework. This is a tough one and can't be fully explained in forum message, need a real worksop because depends a lot of you, your goal, your team, your business, .. But in a quick guidance view, below my way of doing : - Have technical object to manage things not related to business/TC (e.g. Services, Cmd, System, DB, ..). - Have one object dedicated to one app; - Build this object as OOP as you can - Build it with at least 3 layers; base function not linked to business, base business function and complex business function. - Think about useful properties (e.g. Performance or Functiunal exec mode, log level, base path, ..) - Use documentation framework (i use jsdoc style comments and a tweaked jsdoc module of nodejs) - Include self test in your object 2.2 Usage Several techniques, pseudo-class, closure, useunit, modules ... depends again of multiples things. Personnally i use a closure object method and 2 units (one for global keys mappings/variables, one for business object/methods). And the call from tests cases is made by //USEUNIT Re: Why Web object ID are changed when upgraded to TestComplete 14.73 TC doesn't change the value of data that it introspect. In Angular ids are often auto generated root based name so are you sure that is TC the problem ? Re: Advanced search object for complex model hkim5 i use it thoroughly in a complex WPF application and it save me a lot 😉 Another side effect of using it is that is a little quicker than the sequential call of findChildEx() Here a simplified version without debug level and isolated from my framework : function findContainer(ParentObject = null, Props = null, Values = null, Depth = 4) { // Vérification des paramètres obligatoires if ((ParentObject == null) || (Props == null) || (Values == null)) return null; var objectfind = ParentObject; let currentProps; let currentValues; let currentDepth; try { for (let i=0;i<Props.length;i++) { currentProps = Props[i].split('|'); currentValues = new Array(); currentDepth = typeof Depth == 'number' ? Depth : Depth[i]; for (let j=0;j<currentProps.length;j++) { currentValues.push(Values.shift()); } objectfind = objectfind.FindChildEx(currentProps, currentValues, currentDepth, true, 5000); if ((typeof objectfind == 'undefined') || ((objectfind != null) && (!objectfind.Exists))) break; } } catch (e) { objectfind = null; } finally { // Ne renvoyer que "null" sur non trouvé ou erreur if ((typeof objectfind == 'undefined') || ((objectfind != null) && (!objectfind.Exists))) objectfind = null; return objectfind; } }