ContributionsMost RecentMost LikesSolutionsRe: Several log paths when exporting logs from test execute command line. Use the Log.SaveResultsAs method in a tear down method for your test suite: var outPath = ResultIsOK ? 'passed' : 'failed'; Log.SaveResultsAs(qa.variables.getLogFilePath() + "\\" + outPath + \\" + qa.variables.getTestSuiteName() + ".mht", lsMHT) (qa.variables.getLogFilePath and qa.variables.getTesteSuiteName must be replaced by your own variables) But imho would be better to use a test management sofware (a good free one is Testlink). Re: playback now does entire project instead of single function Does you try to run your script routine from the Project Explorer tree ? - Select the wanted script in Project Suite tree - Right-click on it - Highlight the Run menu item and unfold its sub-items - Click on the desired routin to play Is it playing all Project Suite also with this method ? Re: A new feature to generate script documentation(HTML file like JsDocs) I use Jsdoc and a custom template to produce documentation of our framework in html website (see attached printscreen). By tweaking the code of jsdoc you can tell him to accept your kind of comments (which is really not a standard one). But i agree that a feature to generate automatically documentation would be nice (even if building doc with jsdoc it is very easy and fast) so +1 Re: identify dynamic objects properties * i don't know exactly what you want but i've made the following method to search by Xpath query inside web page. /** * <a id="web.findItWeb"></a> * Rechercher des objets via requête XPath sur une page web<br> * <br> * <font color=Red><i><b>Limitations<br> * - limitation technique, TestComplete ne gère que les requêtes XPath 1.0</b></i></font> * @function * @param {string} Property - Nom de la propriété à rechercher * @param {variant} Value - Valeur de la propriété à rechercher * @param {string} [Type=*] - Type de l'objet (<i>SELECT</i>, <i>INPUT</i>, <i>DIV</i>, etc..) pour réduire le champ de recherche * @param {object} [Page=current page] - Page/élément web à rechercher dedans * @param {number} [Timeout=0] - Si <b>>0</b> alors répéter la recherche pendant <i>Timeout</i> millisecondes * @param {boolean} [Refresh=false] - <b>true</b> alors rafraichir la page avant de faire la recherche * @param {boolean} [Parent=false] - <b>true</b> alors renvoyer l'objet parent de celui recherché * @param {boolean} [Partial=false] - <b>true</b> alors recherche sur <i>Property contient Value</i> et non pas <i>Property = Value</i> * @returns {object} Renvoie un <b>objet web TestComplete</b> si il a été trouvé ou <b>null</b> s'il n'a pas été trouvé */ qa.web.findItWeb = function (Property, Value, Type, MyPage, Timeout, Refresh, Parent, Partial) { if ((Property == undefined) || (Value == undefined)) { if (qa.system.findDebug) { Log.Warning('Appel de qa.web.findItWeb() avec un paramètre obligatoire non renseigné (Property ou Value)'); } return null; } MyPage = qa.system.checkUndefined(MyPage, Sys.Browser().Page('*')); Type = qa.system.checkUndefined(Type, "*"); Refresh = qa.system.checkUndefined(Refresh); Parent = qa.system.checkUndefined(Parent); Partial = qa.system.checkUndefined(Partial); Timeout = qa.system.checkUndefined(Timeout, 0); var toFind; if (qa.system.findDebug) { var TimeoutStr = (Timeout == 0 ? 'no time out' : Timeout.toString()); Log.Message('qa.web.findItWeb(' + Property.toString() + ', ' + Value.toString() + ', ' + Type.toString() + ', [MyPage], ' + TimeoutStr + ', ' + Refresh.toString() + ', ' + Parent + ', ' + Partial + ')'); } if (Partial) { toFind = "//" + Type + "[contains(@" + Property + ', "' + Value + '")]'; } else { toFind = "//" + Type + "[@" + Property + ' ="' + Value + '"]'; } if (Parent) { toFind = toFind + '/..'; } var objectfind; try { if (Timeout == 0) { if (Refresh) { MyPage.Refresh(); } objectfind = MyPage.EvaluateXPath(toFind); if (objectfind != null) { objectfind = (new VBArray(objectfind)).toArray(); } } else { var finding = false; var chrono = HISUtils.StopWatch; chrono.Start(); while ((!finding) && (chrono.Split() < Timeout)) { if (Refresh) { MyPage.Refresh(); } objectfind = MyPage.EvaluateXPath(toFind); if (objectfind != null) { objectfind = (new VBArray(objectfind)).toArray(); finding = objectfind.Exists; } } } } catch(e) { objectfind = null; } finally { if (qa.system.findDebug) { if ((objectfind == undefined) || (objectfind == null)) { Log.Message('qa.web.findItWeb() renvoie un objet null ou undefined'); } else { Log.Message('qa.web.findItWeb() a trouvé un objet'); } } return objectfind; } }; Adapt it to your code (here it's a method part of my framework namespace qa.web so method signature is quite unusual) For example to search a DIV item by it's html class (webobject could be the Sys.Browser('*').Page('*') or an object of a page): item = qa.web.findItWeb('class', 'name_of_the_class_to_look_for', 'DIV', webobject; Or searching a SELECT item by it's html name: var Page = Sys.Browser().Page(_SHRoute); var TypeRoute = qa.web.findItWeb("name", "name_to_look_for", "SELECT", Page); TypeRoute[0].ClickItem(_SHRessources[pRouteType]); Or searching an INPUT item ... var FileRoute = qa.web.findItWeb("name", "name_to_look_for", "INPUT", Page); Or searching an item only by it's id on the current page ... var ItemToFind = qa.web.findItWeb("id", "id_to_look_for"); * To set text to an input i've made the following method, i let you dig inside a little and try it and if you still need help tell me. /** * <a id="web.setTextInput"></a> * Saisir un texte dans un champ de saisie d'un objet web * @function * @param {Object} TextInput - Objet web contenant une propriété texte acceptant la saisie * @param {string} Value - Texte à saisir * @param {boolean} [Validate=false] - <b>true</b> alors appuyer sur <i>ENTREE</i> après saisie * @param {string} [PropertyName=caption] - Nom de la propriété de <i>TextInput</i> à utiliser * @param {boolean} [Force=false] - <b>true</b> alors saisir et ignorer le contrôle de la valeur saisie * @param {boolean} [Autocomplete=false] - <b>true</b> alors vérifier à chaque saisie de caractère si par autocomplete la valeur du champ est bien renseignée (incompatible avec le paramètre <i>Force</i>) * @returns {boolean} <b>true</b> si l'opération s'est bien déroulée et, si <i>Force</i> est à <b>false</b>, que la valeur demandée est bien saisie */ qa.web.setTextInput = function (TextInput, Value, Validate, PropertyName, Force, Autocomplete) { if ((TextInput == undefined) || (TextInput == null)) { return false; } Validate = qa.system.checkUndefined(Validate); PropertyName = qa.system.checkUndefined(PropertyName, 'caption'); Force = qa.system.checkUndefined(Force); Autocomplete = qa.system.checkUndefined(Autocomplete); var resultat = true; try { if (Force) { TextInput.Click(); TextInput.Keys('^a'); TextInput.Keys('[Del]'); TextInput.Keys(Value); if (Validate) { TextInput.Keys('[Enter]'); } } else { var i = 0; if (Autocomplete) { var j; while ((!(TextInput.WaitProperty(PropertyName, Value, qa.system.time.micro))) && (i<10)) { TextInput.Click(); TextInput.Keys('^a'); TextInput.Keys('[Del]'); var tableau=Value.split(""); for (var j=0; j<tableau.length; j++) { TextInput.Click(); TextInput.Keys(tableau[j]); aqUtils.Delay(qa.system.time.smaller, 'Autocomplete text input'); if (TextInput.WaitProperty(PropertyName, Value, qa.system.time.micro)) { break; } } i++; } } else { while ((!(TextInput.WaitProperty(PropertyName, Value, qa.system.time.micro))) && (i<10)) { TextInput.Click(); TextInput.Keys('^a'); TextInput.Keys('[Del]'); TextInput.Keys(Value); i++; } } if (TextInput.WaitProperty(PropertyName, Value, 0)) { if (Validate) { TextInput.Keys('[Enter]'); } } else { resultat = false; } } } catch(e) { resultat = false; } return resultat; }; Re: Button Does not Respond when Called from Script in IE but does in Chrome. Does an IE update occured ? Or a setting changed (compatibility mode, ..) ? Or the tested application changed ? Re: How to temporay disconnect a GeneralEvents handler ? The algorithm of my event handler is (give it for other people if they can grab ideas): Same MessageText than previous message ? Yes-> increment repetition counter and lock this Log.Error No -> Check if error message is a known-to-be-ignored one ? Yes-> make a Log.Message instead of a Log.Error and lock this Log.Error No -> Check if repetition message counter is > 0 ? Yes->as the message has changed then make a unique Log.Message for the repeated message Make a Log.Error entry and reset repetition counter as well as previous messagebackup and lock this Log. So after your interesting replies i think i'll keep my solution for now. The global variable solution works well also but need more lines of code and add external variable. The splitting method is ok also but add another function. Perhaps it's the more elegant because adding more flexibility but it's the heavier. Txs for your ideas. How to temporay disconnect a GeneralEvents handler ? Hello all, Need your help to be sure there is not a better way than mine to achieve my problem. I use GeneralEvents to make a standard wrapper on Log.Message, Log.Warning, Log.Message offering me a way to application-wide bypass some errors and manage multiple same message in log. For example below is the code of the Log.Error wrapper; /* --------------------------------------------------------------------------- Gérer les erreurs répetées via gestion des events --------------------------------------------------------------------------- */ function GeneralEvents_OnLogError(Sender, LogParams) { if (LogParams.MessageText == Globals.LASTERRORLOG) { Globals.SAMEERRORCOUNT++; LogParams.Locked = true; } else { // Pour éviter un test rouge // - par l'erreur aléatoire de IT_Launch à sa sortie, // - sur une déconnexion de la VM // - ou sur une VM non visible // - ou sur la validation d'identification qui n'a pas d'élément var skip = aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'exception was encountered while terminating', false, lmNone); if (!skip) { skip = aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'is invisible', false, lmNone); } if (!skip) { skip = aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'because the user session is disconnected', false, lmNone); } if (!skip) { skip = aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'process not found', false, lmNone); } if (skip) { Log.Message(LogParams.MessageText); LogParams.Locked = true; } else { // Transformer les erreurs d'objet inexistants en warning if ((Globals.IGNORENOTFOUND) && ((aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'Unable to find the object', false, lmNone)) || (aqObject.CompareProperty(LogParams.MessageText, cmpContains, 'The object does not exist.', false, lmNone)))) { Log.Message(LogParams.MessageText, LogParams.AdditionalText, pmHigher, qa.system.logWarning); LogParams.Locked = true; } else { // Gérer les répétitions if (Globals.SAMEERRORCOUNT > 1) { Log.Message('Erreur "' + Globals.LASTERRORLOG + '" répétée ' + Globals.SAMEERRORCOUNT.toString() + ' fois !', '', pmNormal, qa.system.logInfo); } } Globals.LASTERRORLOG = LogParams.MessageText; var f = GeneralEvents_OnLogError; GeneralEvents_OnLogError = null; Log.Error(Globals.LASTERRORLOG, LogParams.AdditionalText, pmHighest, qa.system.logError); GeneralEvents_OnLogError = f; Globals.SAMEERRORCOUNT = 0; LogParams.Locked = true; } } } At a certain time i need to really write down a Log.Error (when the error is the first one and it's not a known case to be ignored) but if i fire a Log.Error the event handler is called. Solution is this; // Backup the event handler var f = GeneralEvents_OnLogError; // Remove it GeneralEvents_OnLogError = null; // Fire the Log.Error Log.Error(Globals.LASTERRORLOG, LogParams.AdditionalText, pmHighest, qa.system.logError); // Restore the event handler GeneralEvents_OnLogError = f; Is there any more efficient/elegant way to do that ? Re: How to select invisible row on the screen of a TreeView Object i don't know your project but sometimes data aren't populated till they need to be shown (and it's not a TC problem but codingoptimization for faster load). So you can use the following trick; select your treeview, and use arrows to navigate until accessing the desired item and thus it is displayed and therefore can be accessed. - Call treeViewObj.SelectItem( 0 , 0) - Key down until the desired row index - Call treeViewObj.Clicktem( rowIndex , 0) BTW this is more the reality because an user cannot click on invisible item so for testing it's better. Re: Unable to get the object when the software is in (Not Responding) state About keys not working, don't use the testobject Keys but use the LLPlayer instead of. This insert a WM_Key message directly in windows loop and thus by-pass any code restriction So to send a real ENTER key press to system ; LLPlayer.KeyDown(VK_RETURN, 5); LLPlayer.KeyUp(VK_RETURN, 5); First parameter is virtual key code, list of them ishere. Second optional parameter is delay, better to use it to simulate real-life press which is not INSTANT. BTW you can use the same for mouse message, so a left click is: LLPlayer.MouseDown(MK_LBUTTON, xcoord, ycoord, 25); LLPlayer.MouseUp(MK_LBUTTON, xcoord, ycoord, 25); First parameter is the mouse button. Second and third parameters are coordinates. Fourth parameter is delay, better to use it to simulate real-life press which is not INSTANT. Be careful, the mouse or keyboard event is sent to the system and thus is treated by the currently active windows. For a clik or a press, you need always to do both actions, down and up For more information, search for LLPlayer in TC help. Hope it helps. Re: How to validate if file is created (on disk) In addition you can check if folder exists with this function: function folderExists(Folder) { var result = false; try { result = aqFileSystem.GetFolderInfo(Folder).Exists; } catch(e) { } return result; }; The Folder parameter may or may not include the trailing backslah, no matter.