Showing posts with label XPCOM. Show all posts
Showing posts with label XPCOM. Show all posts

Sunday, December 28, 2008

Can Selenium detect if the page has JavaScript errors?

We usually have common requirement for Selenium: Check JS error in step.

Use case like:
1. beginJsErrorChecker
2. open | http://containJSError.jsp
3. open | http://noissueJS.jsp
4. endJsErrorChecker
It can report url2 has jserror in Selenium:
[error] JsErrorChecker:[JavaScript Error: "foo is not defined" {file: "http://containJSError.jsp" line: 9}]
It's hard to provide a generic solution for all browsers but my team implements above scenario for Selenium IDE only by Firefox chrome Error Console XPCOM APIs.

user-extensions.js like:
 
if (browserVersion.isChrome) {
  var theConsoleListener = {
     stepIdx:0, //beginJsErrorChecker command index
     observe:function( aMessage ){//async!
        dump("Log : " + aMessage.message);
        //Error, Warning, Message too noise. We only report "[JavaScript Error:..."
        if(aMessage.message != null && aMessage.message.indexOf("[JavaScript Error:") == 0){ 
          LOG.error("JsErrorChecker:" + aMessage.message);
                  
          //throw new SeleniumError("Catch JS Error: " + aMessage.message);
          //can't throw exception here: pollution to target JSError
          //so we manually markFailed
          
          //IDE only
          var olddebugIndex = testCase.debugContext.debugIndex;
          testCase.debugContext.debugIndex = this.stepIdx;//set actual index
                  
          testCase.debugContext.failed = true;
          testCase.debugContext.currentCommand().result = 'failed';
          editor.view.rowUpdated(testCase.debugContext.debugIndex);
                  
          testCase.debugContext.debugIndex = olddebugIndex;//recover
        } 
   },
   QueryInterface: function (iid) {
        if (!iid.equals(Components.interfaces.nsIConsoleListener) &&
              !iid.equals(Components.interfaces.nsISupports)) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
            }
        return this;
   }
  }; 
}
 
Selenium.prototype.doBeginJsErrorChecker = function(){
  try {
    if (browserVersion.isChrome) {// firefox 
          theConsoleListener.stepIdx=testCase.debugContext.debugIndex;//set current step idx for async call
          var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
                          .getService(Components.interfaces.nsIConsoleService);
          aConsoleService.registerListener(theConsoleListener);
          aConsoleService.reset();
    }else{
      throw new SeleniumError("TODO: Non-FF browser...");
    }
    } catch (e) {
    throw new SeleniumError("Threw an exception: " + e.message);
    }
};

Selenium.prototype.doEndJsErrorChecker = function(){
  try {
    if (browserVersion.isChrome) {// firefox  
          var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
                          .getService(Components.interfaces.nsIConsoleService);
          aConsoleService.unregisterListener(theConsoleListener);
          aConsoleService.reset();
    }else{
      throw new SeleniumError("TODO: Non-FF browser...");
    }
    } catch (e) {
    throw new SeleniumError("Threw an exception: " + e.message);
    }
};
I just add a Selenim listener to Firefox Error Console to make jserror checker work for Selenium-IDE.
Because it is async mode to check JSError, we treat beginJsErrorChecker failed if any step contains JSError in begin...end block.
We also filter all Warning and Message in Error Console, treat Error only as failed here.

I pasted solution on http://clearspace.openqa.org/message/52135
http://jira.openqa.org/browse/SEL-613

Sunday, December 21, 2008

Two Javascript synchronous sleep functions which won't freeze browser

Javascript didn't provide Thread.sleep(ms) method like Java which is useful for synchronization.

Most of time we can solve Javascript synchronization by use setTimeout or setInteval. However, using setTimeout requires you to split your function up into discrete processes, which is hard to do for some Javascript logic. What we really need is a free-standing function that could be added inline to any block of code to share the processor on the fly.

I encounter a scenario in Selenium today: I want Javascript to sleep to wait for an async call. It's much harder than I thought to implement it. Think about below code:
function sleep(delay) {
   var startTime = new Date();
   var endTime = null;
   do {
       endTime = new Date();
   } while ((endTime - startTime);
} 
It looks fine but if you try it, you will find CPU is very busy executing the while loop and freeze browser. This is not what we wanted.

I got 2 workaround methods. The keypoint is JS NOT freeze CPU/browser during sleep:
1)javascript sleep 1: use XMLHttpRequest to do sync call for sleep.jsp
function wbsleep(ms) { 
  var   startDate = new Date();   
  while((new Date()-startDate) < ms){   
      var xmlHttpReq; 
      try {
         // for IE/ActiveX
         if(window.ActiveXObject) {
             try {
                 xmlHttpReq = new ActiveXObject("Msxml2.XMLHTTP");
             }
             catch(e) {
                 xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
             }
         }
         // Native XMLHttp
         else if(window.XMLHttpRequest) {
             xmlHttpReq = new XMLHttpRequest();
         } 
                tempurl = "http://localhost/sleeputil.jsp?sleepms="+ms;
         xmlHttpReq.open("GET", tempurl, false); // synchron mode  
         xmlHttpReq.send(null);
      }
      catch(e) {  
      } 
  }
};
The sleeputil.jsp is very simple, get request parameter sleepms and then do a Java Thread.sleep(sleepms) in doGet. This method is suitable for all browsers but need you provide a server side sleeputil.jsp.

2)javascript sleep 2: XPCOM on Firefox chrome only
/**
 * Netscape compatible WaitForDelay function.
 * You can use it as an alternative to Thread.Sleep() in any major programming language
 * that support it while JavaScript it self doesn't have any built-in function to do such a thing.
 * parameters:
 *  (Number) delay in millisecond
*/
function nsWaitForDelay(delay) {
  try{
    /**
    * Just uncomment this code if you're building an extention for Firefox.
    * Since FF3, we'll have to ask for user permission to execute XPCOM objects.
    */
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

    // Get the current thread.
    var thread = Components.classes["@mozilla.org/thread-manager;1"].getService(Components.interfaces.nsIThreadManager).currentThread;

    // Create an inner property to be used later as a notifier.
    this.delayed = true;

    /* Call JavaScript setTimeout function
     * to execute this.delayed = false
     * after it finish.
     */
    setTimeout("this.delayed = false;", delay);

    /**
     * Keep looping until this.delayed = false
    */
    while (this.delayed) {
        /**
         * This code will not freeze your browser as it's documented in here:
         * https://developer.mozilla.org/en/Code_snippets/Threads#Waiting_for_a_background_task_to_complete
        */
        thread.processNextEvent(true);
    }
  }catch(e) {  
  } 
}
This is especially suitable for Selenium CORE*ffchrome.

Saturday, December 20, 2008

Automate HttpFox(HttpWatch) test case for http traffic manipulation in Selenium

Selenium can verify and store values by DOM on current page easily. However, it's hard for Selenium to do manipulation on http traffic like POST Data, http headers, Query String and Content sniffer.

I know many QA did http traffic testing manually by 2 famous software:
1) HTTPWatch: commercial. support both IE and Foxfox.
2) HTTPFox: free, opensource. a Firefox extension.

One day, a guy came to my cube and raised a question: Can I stop my tons of manual functional testing with HttpFox, but use Selenium to automate them? Is it possible that we create a bundle of Selenium APIs to manuplate Http Traffic Sniffer operations?

Yes, we should add this important feature to Selenium. I finished a beta version to support HttpFox APIs in Selenium:
1) merge httpfox.xpi into Selenium-IDE.xpi
2) leverage XPCOM in httpfoxservice.js HTTPFox provided
3) create Selenium APIs and print http traffic info to Selenium LOG:
startHttpFox4Sel
stopHttpFox4Sel
clearHttpFox4Sel
verifyTextInHttpFoxURLs(Not)Present | textpattern
verifyTextInHttpFoxContent(Not)Present | urlpattern | textpattern
storeContentInHttpFoxByURL | urlpattern | varname
verifyParamInHttpFoxQueryString(Not)Present | urlpattern | param1=value1,param2=value2 list
storeParamInHttpFoxQueryString | param@urlpattern | varname
verifyParamInHttpFoxPostData(Not)Present | urlpattern | param1=value1,param2=value2 list
storeParamInHttpFoxPostData | param@urlpattern | varname
...

Source draft:
Selenium.prototype.doStartHttpFox4Sel = function() {
    try {
     if(browserVersion.isChrome){
      HttpFox.cmd_hf_clear();
         HttpFox.cmd_hf_startWatching(); 
        }
    } catch (e) {
     throw new SeleniumError("Threw an exception: " + e.message);
    }
}; 

Selenium.prototype.doStopHttpFox4Sel = function() {
    try {
        if(browserVersion.isChrome){ 
         HttpFox.cmd_hf_stopWatching();
         HttpFox.cmd_hf_clear();
        }
    } catch (e) {
     throw new SeleniumError("Threw an exception: " + e.message);
    }
}; 

Selenium.prototype.doClearHttpFox4Sel = function() {
    try {
        if(browserVersion.isChrome){  
         HttpFox.cmd_hf_clear();
        }
    } catch (e) {
     throw new SeleniumError("Threw an exception: " + e.message);
    }
}; 
       
Selenium.prototype.isTextInHttpFoxURLsPresent = function(pattern) {
   try {
     if(browserVersion.isChrome){  
   for (var i = 0; i < HttpFox.HttpFoxService.Requests.length; i++){ 
        var request = HttpFox.HttpFoxService.Requests[i];
        //1.Time:
        var time = formatTimeDifference(request.StartTimestamp, request.EndTimestamp) ;
        //2.Sent:
        var sent = "";
        if (request.IsSending){
          sent = humanizeSize(request.getBytesSent(), 6) + "/" + humanizeSize(request.getBytesSentTotal(), 6);
        }
        else {
         sent = humanizeSize(request.getBytesSentTotal(), 6); 
        } 
        //3.Received:     
         var received = "";
         if (request.IsSending){
          received = "*";
       }
       if (!request.IsFinished){
          // show loading body progress
         received = humanizeSize(request.getBytesLoaded(), 6) + "/" + humanizeSize(request.getBytesLoadedTotal(), 6);
       }else{
         received = humanizeSize(request.getBytesLoaded(), 6); 
       }
       if (request.IsFromCache || request.ResponseStatus == 304){
         received = "(" + received + ")";
       } 
        //4.Method:
       var method = request.RequestMethod; 
        //5.Result: 
        var result = "";
       if (request.IsAborted){
         result =  "(Aborted)";
       }else if (request.isError()){
          result = "(Error)";
       }else if (request.IsFromCache && (request.ResponseStatus != 304)) {
          result =  "(Cache)";
       }else if (!request.HasReceivedResponseHeaders && !request.IsFinal){
          result = "*";
       }else{
           result =  request.ResponseStatus;
       } 
        //6.Type:
              var type = "";
              if (request.hasErrorCode()){
               if (request.ContentType){
               type =  request.ContentType + " (" + request.Status.toString(16) + ")";//we omit nsResultErrors translate
              }else{
               type =  request.Status.toString(16);
              }
             }else if (!request.HasReceivedResponseHeaders && !request.IsFromCache && !request.IsFinal){
               type =  "*";
             }else if (request.isRedirect()){
              if (request.ResponseHeaders && request.ResponseHeaders["Location"]){
               type =  "Redirect to: " + request.ResponseHeaders["Location"]; 
              }else{
               type =  "Redirect (cached)";
              }
             }else{
              type = request.ContentType;     
             }  
       //7.URL
       var urlstring = request.Url ;
       //TODO: LOG.info(http sniffer info);
       //TODO: verify as you like by PatternMatcher
   } //end for
    }//end chrome
    } catch (e) {
     throw new SeleniumError("Threw an exception: " + e.message);
    }
}; 

Now, QA can automate HttpFox steps in Selenium. They don't need to do http traffic testing manually all days.

Friday, November 14, 2008

Include JS src file in Firefox Chrome Javascript file

To include a Javascript src file in HTML, it's very simple:
<script src="scripts/b.js" type="text/javascript"></script>
However, how to implement above logic in a Chrome js file(XUL missed including b.js).
There is a XPCOM API @mozilla.org/moz/jssubscript-loader;1
Sample:
const subScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
 //file:/// path for XPCOM. http:// not support
subScriptLoader.loadSubScript('file:///c:/scripts/b.js', this);
In normal js file(non-chrome), it's also simple.
Sample:
document.write('<script src="scripts/b.js" type="text/javascript"></script>');
This tip is useful for Selenium-IDE enhancement.

Thursday, November 13, 2008

Security Alerts Interceptor for Selenium-IDE

There are 2 kinds of HTTPs Certification Security Alerts in Firefox.
1) Domain name mismatch
2) Certificate expired
Read http://kb.mozillazine.org/Security_Error:_Domain_Name_Mismatch_or_Server_Certificate_Expired
Unfortunately, Firefox 2 didn't provide a preferences entry to disable or hidden alerts.
These annoyed alerts slow down selenium speed and block selenium automation if we don't click out alerts.

There is a Firefox extension Remember Mismatch Domains(RMD.xpi) to add a "Don't warn me again about this certificate for this domain" checkbox to the Domain Mismatch and Expired Certificate warning windows. When selected the domain name and security certificate domain pair (or certificate and expiration date pair) is stored in a Firefox preference and the security error dialogue will be bypassed on subsequent visits.

The author provided a XPCOM to destroy Security Alerts Window before it displays in Firefox. We can integrate this XPCOM with Selenium-IDE and provide Security Alerts Interceptor feature.

Here are steps and code:
1) copy /platform folder from RMD.xpi to selenium-ide.xpi
2) copy rmdBadCertHandler.js and rmdIBadCertHandler.xpt to /components folder
3) edit rmdBadCertHandler.js
_isRemembered: function (handler_type, target_url, cert) {
   return true;
   //intercept and kill all security alerts
   //or add a preference entry here and return true or false
   ...
}
NOTE: it is used for Firefox 2 only. Firefox 3 has a new security exception handler.

Tuesday, November 11, 2008

Selenium close all Firefox windows in profile when finish run

There is a Chrome URL parameter &close=true in Selenium-IDE. When set to true, it will automatically close Selenium Firefox windows when finish run. However, if application window has popuped child windows like ads, selenium can't close these popup windows when quit.

This has risk to lock Firefox profile when multiple Firefox profiles running simuteniously. Please refer to "Firefox is aleady running issue"
Enhance Selenium-IDE by XPCOM API: close all Firefox windows under current profile. Add logic when Selenium quit(close=true).
Sample code:
var windowManager = Components
  .classes['@mozilla.org/appshell/window-mediator;1'].getService();
var windowManagerInterface = windowManager
  .QueryInterface( Components.interfaces.nsIWindowMediator);
var enumerator = windowManagerInterface.getEnumerator( null );
var appStartup = Components
  .classes['@mozilla.org/toolkit/app-startup;1'].
  getService(Components.interfaces.nsIAppStartup);
while ( enumerator.hasMoreElements()  )
{
  var domWindow = enumerator.getNext();
  if (("tryToClose" in domWindow) 
       && !domWindow.tryToClose())
    return false;
  domWindow.close();
};
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);

Monday, November 10, 2008

Save to local files with XPCOM for Selenium Firefox Chrome

We save selenium run snapshot/result/log to local files instead of complex POST to server solution. We require APIs in browser(out of Security Sandbox) for local files accessing.

In IEHTA, there is ActiveX object FSO to access local files.
var objFSO = new ActiveXObject("Scripting.FileSystemObject");
Is there a similar object in Mozilla Firefox Chrome?
The answer is XPCOM object @mozilla.org/file/local;1

Save to file Sample:
function saveFile(fileName, val){
   try {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   } catch (e) {
      //alert("Permission to save file was denied.");
   }
   var file = Components.classes["@mozilla.org/file/local;1"]
             .createInstance(Components.interfaces.nsILocalFile);
   file.initWithPath(fileName);
   if (file.exists() == false) {
     //alert("Creating file... " );
     file.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420 );
   }
   var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
            .createInstance(Components.interfaces.nsIFileOutputStream);
   outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 ); 
   //UTF-8 convert
   var uc = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
     .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
   uc.charset = "UTF-8";
   var data_stream = uc.ConvertFromUnicode(val);
   var result = outputStream.write(data_stream, data_stream.length );
   outputStream.close(); 
}
We use UTF-8 encode here.

Friday, November 7, 2008

Build a regression automation testing framework based on Selenium FIT mode

When company only has 100 regression test cases written by Selenese, QA steps to do regression tasks by Selenium FIT mode:
1) Organize test cases files with suites files on local driver or shared driver
2) Open Chrome TestRunner in Firefox
3) Import test suites address in file sytem, click run
4) Monitor and wait for run finished, manually collect run result and write a run report with generic result info.

Assume your company business's growth and you have 1000+ regression test cases.
How to execute these cases in a short time and generate report with detailed enough info automatically?
0) All QA upload and maintain test cases on a central portal.
1) Choose test cases you want to kick off regression from case repository
2) Specify necessary parameter for regression and kick off
3) A scalable machine matrix in backend begin to do automation run parallelly. There are also multiple instances running simuteniously on same machine. Each instance used Selenium FIT engine. The run snapshot/result/log are stored into filesytem/DB runtimely by above instances called Agent.
4) When all run finished, a report can be generated from file sytem/DB.

It's feasible to implement such a system with Selenium FIT mode.

Comparing to similar system based on S-RC+S-GRID:
1) No need proxy server, more lightweight framework. No need precompile like .java test case source.
2) Firefox Chrome TestRunner is more stable and faster than RC Runner. It can bypass many issues which RC can't resolve. Chrome XPCOM APIs is very powerful for develop Selenium new features.
3) User only need to know how to create a Selenese test case, no other skill requirement.