Make JavaScript Errors available to test cases

Registered by Stephane Mikaty (eCircle AG)

Motivation:
It is currently not possible to know whether a web application under test:
- Caused JavaScript compilation errors
- Caused JavaScript runtime errors
- Caused the JavaScript on a page to call alert()

There is a check for alert() called when a Form submission has timed out, but this
is no longer sufficient. Many web apps out there are using JavaScript heavily and
the inability to retrieve such messages is making itself felt in TestPlan.

Note: Even Selenium does not give the ability to get JavaScript compilation / runtime
errors, so the problem is not specific to TestPlan itself

Proposal: Extend ResponseState with the following method, to be implemented for each
type of ResponseState (although Selenium is our current priority, and we're willing
to contribute the code for it).

----------------------------------------------------------------------------
Selenium implementation Suggestion
----------------------------------------------------------------------------

public interface ResponseState {
...
 /**
  * Consumes all the messages from the underlying implementation of
  * this {@code ResponseState}, resetting messages as it does so.
  * That is, calling consumeMessages() never returns the same logical
  * message more than once. The caller is responsible for handling
  * messages and reacting to them by failing the test case, logging
  * the messages to a log file, etc...
  *
  * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIScriptError,
  * which inspires the design of messages.
  * @return Collection of {@code testplan.auto.ResponseState.Message}s
  * each containing more information about the severity and body
  * of the message.
  */
 public Collection<Message> consumeMessages();

 /**
  * Message wrapper interface
  */
 public interface Message {

  /**
   * @return message attributes keyed by name
   */
  Map<String,Object> getAttributes();

  /**
   * @return text of the message
   */
  String getText();

 }
...
}
----------------------------------------------------------------------------
public class BrowserState implements ResponseState {
...
 @Override
 public Collection<Message> consumeMessages() {
  LinkedList<Message> messages = new LinkedList<Message>();
  if ( browser.isAlertPresent() ) {
   final String alert = browser.getAlert();
   messages.add(new Message() {

    @Override
    public String getText() {
     return alert;
    }

    @Override
    public Map<String, Object> getAttributes() {
     return Collections.emptyMap();
    }
   });
  }
  for ( final String scriptError : browser.usrExtConsumeScriptErrors()) {
   messages.add(new Message() {

    @Override
    public String getText() {
     return scriptError;
    }

    @Override
    public Map<String, Object> getAttributes() {
     return Collections.emptyMap();
    }
   });
  }
  return messages;
 }
...
}
----------------------------------------------------------------------------

public final class AutoSelenium implements Selenium, ModeInstance {
...
 Iterable<String> usrExtConsumeScriptErrors() {
  final String scriptErrorString = this.commandProcessor.doCommand("consumeScriptErrors", EMPTY_STRING_ARRAY);
  // TODO: get a JSON-Stringified string back and deserialize 1-n errors into a collection?
  return Collections.singleton(scriptErrorString);
 }
...
}

----------------------------------------------------------------------------

Blueprint information

Status:
Not started
Approver:
edA-qa
Priority:
Undefined
Drafter:
Stephane Mikaty (eCircle AG)
Direction:
Needs approval
Assignee:
None
Definition:
New
Series goal:
None
Implementation:
Unknown
Milestone target:
None

Related branches

Sprints

Whiteboard

=== modified file 'src/share/com/meterware/httpunit/HTTPUnitResponseState.java'
--- src/share/com/meterware/httpunit/HTTPUnitResponseState.java 2010-04-07 09:27:06 +0000
+++ src/share/com/meterware/httpunit/HTTPUnitResponseState.java 2010-11-11 14:11:05 +0000
@@ -430,4 +430,9 @@
   return this.resp.createDomScriptingHandler().evaluateExpression( expression );
  }

+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return null;
+ }
+
 }

=== modified file 'src/share/testplan/auto/ResponseState.java'
--- src/share/testplan/auto/ResponseState.java 2009-12-17 18:23:53 +0000
+++ src/share/testplan/auto/ResponseState.java 2010-11-11 14:11:05 +0000
@@ -215,4 +215,9 @@
   * Stick to simple numbers, strings and bool.
   */
  public Object evaluateJavaScript( String code ) throws Exception;
+
+ /**
+ * @return error console content for the currently active window.
+ */
+ public String getJavaScriptErrors() throws UnsupportedOperationException;
 }

=== modified file 'src/share/testplan/auto/dommode/DOMResponseState.java'
--- src/share/testplan/auto/dommode/DOMResponseState.java 2010-04-12 08:43:07 +0000
+++ src/share/testplan/auto/dommode/DOMResponseState.java 2010-11-11 14:11:05 +0000
@@ -220,5 +220,10 @@
   public void invoke( UIAction action ) throws Exception {
    unsupported();
   }
+
+ }
+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return null;
  }
 }

=== modified file 'src/share/testplan/auto/email/EmailResponseState.java'
--- src/share/testplan/auto/email/EmailResponseState.java 2010-04-22 08:33:06 +0000
+++ src/share/testplan/auto/email/EmailResponseState.java 2010-11-11 14:11:05 +0000
@@ -203,5 +203,9 @@
    unsupported();
   }
 }
+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return null;
+ }

 }

=== modified file 'src/share/testplan/auto/htmlunit/ResponseState.java'
--- src/share/testplan/auto/htmlunit/ResponseState.java 2010-04-22 08:33:06 +0000
+++ src/share/testplan/auto/htmlunit/ResponseState.java 2010-11-11 14:11:05 +0000
@@ -625,4 +625,10 @@
     testplan.auto.dommode.DOMMode.withBodyNode( resp.getContentAsBytes(), ct, doc ) );
   }
  }
+
+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return null;
+ }
+
 }

=== modified file 'src/share/testplan/auto/selenium/AutoSelenium.java'
--- src/share/testplan/auto/selenium/AutoSelenium.java 2010-04-22 08:06:06 +0000
+++ src/share/testplan/auto/selenium/AutoSelenium.java 2010-11-11 14:36:58 +0000
@@ -407,7 +407,19 @@
  public String getName() {
   return "selenium";
  }
+
+ String usrExtBeginJsErrorChecker() {
+ return this.commandProcessor.doCommand("beginJsErrorChecker", EMPTY_STRING_ARRAY);
+ }
+
+ String usrExtEndJsErrorChecker() {
+ return this.commandProcessor.doCommand("endJsErrorChecker", EMPTY_STRING_ARRAY);
+ }

+ String usrExtGetJSErrors() {
+ return this.commandProcessor.doCommand("getJSErrors", EMPTY_STRING_ARRAY);
+ }
+
  ///////////////// START OF CUSTOMIZED SELENIUM METHODS

  public void click( String locator ) {
@@ -450,6 +462,7 @@
     clearErrorDumpSuppression();
    }
   }
+ this.usrExtBeginJsErrorChecker();
   delegate.allowNativeXpath("true");
  }

=== modified file 'src/share/testplan/auto/selenium/BrowserState.java'
--- src/share/testplan/auto/selenium/BrowserState.java 2010-09-01 08:28:33 +0000
+++ src/share/testplan/auto/selenium/BrowserState.java 2010-11-11 16:06:46 +0000
@@ -1238,4 +1238,9 @@
    return browser.getEval( jsEvalOnElement( createFormLocator(), expression ) );
   }
  }
+
+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return browser.usrExtGetJSErrors();
+ }
 }

=== modified file 'src/share/testplan/auto/urlmode/HTTPResponseState.java'
--- src/share/testplan/auto/urlmode/HTTPResponseState.java 2010-06-09 11:42:29 +0000
+++ src/share/testplan/auto/urlmode/HTTPResponseState.java 2010-11-11 14:11:05 +0000
@@ -564,4 +564,9 @@
    return buffer.toString();
   }
  }
+
+ @Override
+ public String getJavaScriptErrors() throws UnsupportedOperationException {
+ return null;
+ }
 }

=== added file 'user-extensions.js'
--- user-extensions.js 1970-01-01 00:00:00 +0000
+++ user-extensions.js 2010-10-27 08:55:33 +0000
@@ -0,0 +1,83 @@
+// ==================================================
+// Report Javascript Errors using Selenium Exceptions
+// ==================================================
+// Courtesy of Jerry Qian (http://sejq.blogspot.com/)
+// http://clearspace.openqa.org/message/52135
+// http://jira.openqa.org/browse/SEL-613
+//
+// Adapted to work outside of the Selenium IDE:
+//
+// https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIConsoleListener
+// https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIScriptError
+// https://developer.mozilla.org/en/Setting_up_extension_development_environment#Development_preferences
+
+if (browserVersion.isChrome) {
+ var JSErrors = new Array();
+ var theConsoleListener = {
+ observe:function( aMessage ){//async!
+ if(aMessage instanceof Components.interfaces.nsIScriptError && aMessage.flags != Components.interfaces.nsIScriptError.warningFlag){
+ JSErrors.push(aMessage.message);
+ }
+ },
+ QueryInterface: function (iid) {
+ if (!iid.equals(Components.interfaces.nsIConsoleListener) &&
+ !iid.equals(Components.interfaces.nsISupports)) {
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ }
+ };
+}
+
+// ==================================================
+// Make it possible to dump the *current* state of
+// the DOM from TestPlan
+// ==================================================
+// Courtesy of
+// https://developer.mozilla.org/en/Parsing_and_serializing_XML
+//
+
+Selenium.prototype.doGetDOMSource = function() {
+ var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Components.interfaces.nsIDOMSerializer);
+ return serializer.serializeToString(window.document);
+};
+
+Selenium.prototype.getGetJSErrors = function(){
+
+ var errors = "";
+ if (browserVersion.isChrome) {
+ for (var i=0;i<JSErrors.length;i++) errors += "Error: [" + JSErrors[i] + "];";
+ //alert(errors);
+ } else {
+ throw new SeleniumError("TODO: Non-FF browser...");
+ }
+ return errors;
+};
+
+Selenium.prototype.doBeginJsErrorChecker = function(){
+ try {
+ if (browserVersion.isChrome) {// firefox
+ var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ aConsoleService.registerListener(theConsoleListener);
+ }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);
+ }else{
+ throw new SeleniumError("TODO: Non-FF browser...");
+ }
+ } catch (e) {
+ throw new SeleniumError("Threw an exception: " + e.message);
+ }
+};

(?)

Work Items

This blueprint contains Public information 
Everyone can see this information.

Subscribers

No subscribers.