Serialize errors as JSON

This commit is contained in:
Matthew Horan 2013-02-02 17:32:54 -05:00
parent fe6eff79e6
commit 6c72a99174
24 changed files with 143 additions and 46 deletions

View File

@ -202,26 +202,12 @@ module Capybara::Webkit
if result.nil?
raise NoResponseError, "No response received from the server."
elsif result != 'ok'
case response = read_response
when "timeout"
raise Timeout::Error, "Request timed out after #{timeout_seconds}"
else
raise InvalidResponseError, response
end
raise JSON.parse(read_response)
end
result
end
def timeout_seconds
seconds = timeout
if seconds > 1
"#{seconds} seconds"
else
"1 second"
end
end
def read_response
response_length = @connection.gets.to_i
if response_length > 0

View File

@ -1,5 +1,8 @@
module Capybara::Webkit
class InvalidResponseError < StandardError
def self.json_create(o)
new(o["message"])
end
end
class NoResponseError < StandardError
@ -9,5 +12,14 @@ module Capybara::Webkit
end
class ClickFailed < StandardError
def self.json_create(o)
new
end
end
class TimeoutError < Timeout::Error
def self.json_create(o)
new(o["message"])
end
end
end

View File

@ -342,6 +342,11 @@ describe Capybara::Webkit::Driver do
to raise_error(Capybara::Webkit::InvalidResponseError, /xpath/i)
end
it "raises an error for an invalid xpath query within an element" do
expect { driver.find("//body").first.find("totally invalid salad") }.
to raise_error(Capybara::Webkit::InvalidResponseError, /xpath/i)
end
it "returns an attribute's value" do
driver.find("//p").first["id"].should == "greeting"
end
@ -2059,7 +2064,7 @@ describe Capybara::Webkit::Driver do
it "should raise a timeout error" do
driver.browser.timeout = 1
lambda { visit("/") }.should raise_error(Timeout::Error, "Request timed out after 1 second")
lambda { visit("/") }.should raise_error(Timeout::Error, "Request timed out after 1 second(s)")
end
it "should not raise an error when the timeout is high enough" do

View File

@ -1,4 +1,5 @@
#include "SocketCommand.h"
#include "Command.h"
#include "ErrorMessage.h"
Command::Command(QObject *parent) : QObject(parent) {
}
@ -19,3 +20,6 @@ void Command::emitFinished(bool success, QByteArray message) {
emit finished(new Response(success, message, this));
}
void Command::emitFinished(bool success, ErrorMessage *message) {
emit finished(new Response(success, message, this));
}

View File

@ -1,10 +1,12 @@
#ifndef COMMAND_H
#define COMMAND_H
#include <QObject>
#include "Response.h"
#include <QObject>
#include <QString>
class ErrorMessage;
class Command : public QObject {
Q_OBJECT
@ -17,6 +19,7 @@ class Command : public QObject {
void emitFinished(bool success);
void emitFinished(bool success, QString message);
void emitFinished(bool success, QByteArray message);
void emitFinished(bool success, ErrorMessage *message);
signals:
void finished(Response *response);

View File

@ -6,6 +6,7 @@
#include "PageLoadingCommand.h"
#include "TimeoutCommand.h"
#include "SocketCommand.h"
#include "ErrorMessage.h"
#include <QTcpSocket>
@ -43,9 +44,8 @@ void Connection::pendingLoadFinished(bool success) {
void Connection::writePageLoadFailure() {
m_pageSuccess = true;
QString message = currentPage()->failureString();
Response *response = new Response(false, message, this);
writeResponse(response);
delete response;
Response response(false, new ErrorMessage(message));
writeResponse(&response);
}
void Connection::finishCommand(Response *response) {

26
src/ErrorMessage.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "ErrorMessage.h"
#include "JsonSerializer.h"
ErrorMessage::ErrorMessage(QString message, QObject *parent) : QObject(parent) {
m_message = message;
}
ErrorMessage::ErrorMessage(QString type, QString message, QObject *parent) : QObject(parent) {
m_type = type;
m_message = message;
}
QByteArray ErrorMessage::toString() {
JsonSerializer serializer;
QVariantMap map;
if (m_type.isNull())
map["json_class"] = "Capybara::Webkit::InvalidResponseError";
else
map["json_class"] = m_type;
map["message"] = m_message;
return serializer.serialize(map);
}

21
src/ErrorMessage.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef __ERROR_MESSAGE_H
#define __ERROR_MESSAGE_H
#include <QObject>
#include <QString>
#include <QByteArray>
class ErrorMessage : public QObject {
Q_OBJECT
public:
ErrorMessage(QString message, QObject *parent = 0);
ErrorMessage(QString type, QString message, QObject *parent = 0);
QByteArray toString();
private:
QString m_type;
QString m_message;
};
#endif

View File

@ -1,6 +1,7 @@
#include "Execute.h"
#include "WebPage.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
Execute::Execute(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
}
@ -11,7 +12,7 @@ void Execute::start() {
if (result.isValid()) {
emitFinished(true);
} else {
emitFinished(false, QString("Javascript failed to execute"));
emitFinished(false, new ErrorMessage("Javascript failed to execute"));
}
}

View File

@ -11,7 +11,7 @@ void Find::start() {
InvocationResult result = page()->invokeCapybaraFunction("find", arguments());
if (result.hasError())
return emitFinished(false, QString("Invalid XPath expression"));
return emitFinished(false, result.errorMessage());
QString message;
message = result.result().toString();

View File

@ -2,6 +2,7 @@
#include "SocketCommand.h"
#include "WebPage.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
FrameFocus::FrameFocus(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
}
@ -51,7 +52,7 @@ void FrameFocus::focusId(QString name) {
void FrameFocus::focusParent() {
if (page()->currentFrame()->parentFrame() == 0) {
emitFinished(false, QString("Already at parent frame."));
emitFinished(false, new ErrorMessage("Already at parent frame."));
} else {
page()->currentFrame()->parentFrame()->setFocus();
success();
@ -59,7 +60,7 @@ void FrameFocus::focusParent() {
}
void FrameFocus::frameNotFound() {
emitFinished(false, QString("Unable to locate frame. "));
emitFinished(false, new ErrorMessage("Unable to locate frame."));
}
void FrameFocus::success() {

View File

@ -1,4 +1,5 @@
#include "InvocationResult.h"
#include "ErrorMessage.h"
InvocationResult::InvocationResult(QVariant result, bool error) {
m_result = result;
@ -12,3 +13,17 @@ const QVariant &InvocationResult::result() const {
bool InvocationResult::hasError() {
return m_error;
}
ErrorMessage *InvocationResult::errorMessage() {
if (!m_result.canConvert<QVariantMap>())
return new ErrorMessage(m_result.toString());
QVariantMap error = m_result.toMap();
QString message = error["message"].toString();
if (error["name"] == "Capybara.ClickFailed")
return new ErrorMessage("Capybara::Webkit::ClickFailed", message);
else
return new ErrorMessage(message);
}

View File

@ -1,10 +1,13 @@
#include <QVariant>
class ErrorMessage;
class InvocationResult {
public:
InvocationResult(QVariant result, bool error = false);
const QVariant &result() const;
bool hasError();
ErrorMessage *errorMessage();
private:
QVariant m_result;

View File

@ -17,21 +17,21 @@ QStringList &JavascriptInvocation::arguments() {
return m_arguments;
}
QVariantMap JavascriptInvocation::getError() {
QVariant JavascriptInvocation::getError() {
return m_error;
}
void JavascriptInvocation::setError(QVariantMap error) {
void JavascriptInvocation::setError(QVariant error) {
m_error = error;
}
InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
frame->addToJavaScriptWindowObject("CapybaraInvocation", this);
QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
if (getError().isEmpty())
return InvocationResult(result);
else
if (getError().isValid())
return InvocationResult(getError(), true);
else
return InvocationResult(result);
}
bool JavascriptInvocation::click(QWebElement element, int left, int top, int width, int height) {

View File

@ -10,15 +10,15 @@ class JavascriptInvocation : public QObject {
Q_OBJECT
Q_PROPERTY(QString functionName READ functionName)
Q_PROPERTY(QStringList arguments READ arguments)
Q_PROPERTY(QVariantMap error READ getError WRITE setError)
Q_PROPERTY(QVariant error READ getError WRITE setError)
public:
JavascriptInvocation(const QString &functionName, const QStringList &arguments, WebPage *page, QObject *parent = 0);
QString &functionName();
QStringList &arguments();
Q_INVOKABLE bool click(QWebElement element, int left, int top, int width, int height);
QVariantMap getError();
void setError(QVariantMap error);
QVariant getError();
void setError(QVariant error);
InvocationResult invoke(QWebFrame *);
private:
@ -26,6 +26,6 @@ class JavascriptInvocation : public QObject {
QStringList m_arguments;
WebPage *m_page;
void execClick(QPoint mousePos);
QVariantMap m_error;
QVariant m_error;
};

View File

@ -12,7 +12,7 @@ void Node::start() {
InvocationResult result = page()->invokeCapybaraFunction(functionName, functionArguments);
if (result.hasError())
return emitFinished(false);
return emitFinished(false, result.errorMessage());
QString attributeValue = result.result().toString();
emitFinished(true, attributeValue);

View File

@ -1,6 +1,7 @@
#include "NullCommand.h"
#include "WebPage.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
NullCommand::NullCommand(QString name, QObject *parent) : Command(parent) {
m_name = name;
@ -8,6 +9,6 @@ NullCommand::NullCommand(QString name, QObject *parent) : Command(parent) {
void NullCommand::start() {
QString failure = QString("[Capybara WebKit] Unknown command: ") + m_name + "\n";
emitFinished(false, failure);
emitFinished(false, new ErrorMessage(failure));
}

View File

@ -2,6 +2,7 @@
#include "SocketCommand.h"
#include "WebPage.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
PageLoadingCommand::PageLoadingCommand(Command *command, WebPageManager *manager, QObject *parent) : Command(parent) {
m_manager = manager;
@ -30,7 +31,7 @@ void PageLoadingCommand::pendingLoadFinished(bool success) {
emit finished(m_pendingResponse);
} else {
QString message = m_manager->currentPage()->failureString();
emitFinished(false, message);
emitFinished(false, new ErrorMessage(message));
}
}
}

View File

@ -1,4 +1,5 @@
#include "Response.h"
#include "ErrorMessage.h"
#include <iostream>
Response::Response(bool success, QString message, QObject *parent) : QObject(parent) {
@ -11,6 +12,12 @@ Response::Response(bool success, QByteArray message, QObject *parent) : QObject(
m_message = message;
}
Response::Response(bool success, ErrorMessage *message, QObject *parent) : QObject(parent) {
m_success = success;
m_message = message->toString();
message->deleteLater();
}
Response::Response(bool success, QObject *parent) : QObject(parent) {
m_success = success;
}

View File

@ -5,20 +5,25 @@
#include <QString>
#include <QByteArray>
class ErrorMessage;
class Response : public QObject {
Q_OBJECT
public:
Response(bool success, QString message, QObject *parent);
Response(bool success, QByteArray message, QObject *parent);
Response(bool success, QString message, QObject *parent = 0);
Response(bool success, QByteArray message, QObject *parent = 0);
Response(bool success, ErrorMessage *message, QObject *parent = 0);
Response(bool success, QObject *parent);
bool isSuccess() const;
QByteArray message() const;
QString toString() const;
protected:
QByteArray m_message;
private:
bool m_success;
QByteArray m_message;
};
#endif

View File

@ -1,5 +1,6 @@
#include "SetTimeout.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
SetTimeout::SetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
}
@ -13,7 +14,7 @@ void SetTimeout::start() {
manager()->setTimeout(timeout);
emitFinished(true);
} else {
emitFinished(false, QString("Invalid value for timeout"));
emitFinished(false, new ErrorMessage("Invalid value for timeout"));
}
}

View File

@ -2,6 +2,7 @@
#include "Command.h"
#include "WebPageManager.h"
#include "WebPage.h"
#include "ErrorMessage.h"
#include <QTimer>
#include <QApplication>
@ -45,7 +46,7 @@ void TimeoutCommand::pendingLoadFinished(bool success) {
} else {
disconnect(m_timer, SIGNAL(timeout()), this, SLOT(commandTimeout()));
disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
emitFinished(false, m_manager->currentPage()->failureString());
emitFinished(false, new ErrorMessage(m_manager->currentPage()->failureString()));
}
}
@ -58,7 +59,8 @@ void TimeoutCommand::commandTimeout() {
disconnect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
disconnect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *)));
m_manager->currentPage()->triggerAction(QWebPage::Stop);
emit finished(new Response(false, QString("timeout"), this));
QString message = QString("Request timed out after %1 second(s)").arg(m_manager->getTimeout());
emitFinished(false, new ErrorMessage("Capybara::Webkit::TimeoutError", message));
}
void TimeoutCommand::commandFinished(Response *response) {

View File

@ -3,6 +3,7 @@
#include "WebPage.h"
#include "CommandFactory.h"
#include "WebPageManager.h"
#include "ErrorMessage.h"
WindowFocus::WindowFocus(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
}
@ -12,7 +13,7 @@ void WindowFocus::start() {
}
void WindowFocus::windowNotFound() {
emitFinished(false, QString("Unable to locate window. "));
emitFinished(false, new ErrorMessage("Unable to locate window."));
}
void WindowFocus::success(WebPage *page) {

View File

@ -57,7 +57,8 @@ HEADERS = \
SetUrlBlacklist.h \
NoOpReply.h \
JsonSerializer.h \
InvocationResult.h
InvocationResult.h \
ErrorMessage.h
SOURCES = \
Version.cpp \
@ -116,7 +117,8 @@ SOURCES = \
SetUrlBlacklist.cpp \
NoOpReply.cpp \
JsonSerializer.cpp \
InvocationResult.cpp
InvocationResult.cpp \
ErrorMessage.cpp
RESOURCES = webkit_server.qrc
QT += network webkit