diff --git a/lib/capybara/webkit/browser.rb b/lib/capybara/webkit/browser.rb
index 0f33114..e9ab44f 100644
--- a/lib/capybara/webkit/browser.rb
+++ b/lib/capybara/webkit/browser.rb
@@ -165,6 +165,14 @@ module Capybara::Webkit
command "Render", path, width, height
end
+ def timeout=(timeout_in_seconds)
+ command "SetTimeout", timeout_in_seconds
+ end
+
+ def timeout
+ command("GetTimeout").to_i
+ end
+
def set_cookie(cookie)
command "SetCookie", cookie
end
@@ -199,12 +207,26 @@ module Capybara::Webkit
if result.nil?
raise NoResponseError, "No response received from the server."
elsif result != 'ok'
- raise InvalidResponseError, read_response
+ case response = read_response
+ when "timeout"
+ raise Capybara::TimeoutError, "Request timed out after #{timeout_seconds}"
+ else
+ raise InvalidResponseError, response
+ end
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
diff --git a/spec/driver_spec.rb b/spec/driver_spec.rb
index ee41abf..87ac5ac 100644
--- a/spec/driver_spec.rb
+++ b/spec/driver_spec.rb
@@ -1825,6 +1825,78 @@ describe Capybara::Webkit::Driver do
end
end
+ describe "timeout for long requests" do
+ let(:driver) do
+ driver_for_app do
+ html = <<-HTML
+
+
+
+
+
+ HTML
+
+ get "/" do
+ sleep(2)
+ html
+ end
+
+ post "/form" do
+ sleep(4)
+ html
+ end
+ end
+ end
+
+ it "should not raise a timeout error when zero" do
+ driver.browser.timeout = 0
+ lambda { driver.visit("/") }.should_not raise_error(Capybara::TimeoutError)
+ end
+
+ it "should raise a timeout error" do
+ driver.browser.timeout = 1
+ lambda { driver.visit("/") }.should raise_error(Capybara::TimeoutError, "Request timed out after 1 second")
+ end
+
+ it "should not raise an error when the timeout is high enough" do
+ driver.browser.timeout = 10
+ lambda { driver.visit("/") }.should_not raise_error(Capybara::TimeoutError)
+ end
+
+ it "should set the timeout for each request" do
+ driver.browser.timeout = 10
+ lambda { driver.visit("/") }.should_not raise_error(Capybara::TimeoutError)
+ driver.browser.timeout = 1
+ lambda { driver.visit("/") }.should raise_error(Capybara::TimeoutError)
+ end
+
+ it "should set the timeout for each request" do
+ driver.browser.timeout = 1
+ lambda { driver.visit("/") }.should raise_error(Capybara::TimeoutError)
+ driver.reset!
+ driver.browser.timeout = 10
+ lambda { driver.visit("/") }.should_not raise_error(Capybara::TimeoutError)
+ end
+
+ it "should raise a timeout on a slow form" do
+ driver.browser.timeout = 3
+ driver.visit("/")
+ driver.status_code.should == 200
+ driver.browser.timeout = 1
+ driver.find("//input").first.click
+ lambda { driver.status_code }.should raise_error(Capybara::TimeoutError)
+ end
+
+ it "get timeout" do
+ driver.browser.timeout = 10
+ driver.browser.timeout.should == 10
+ driver.browser.timeout = 3
+ driver.browser.timeout.should == 3
+ end
+ end
+
describe "logger app" do
it "logs nothing before turning on the logger" do
driver.visit("/")
diff --git a/src/CommandFactory.cpp b/src/CommandFactory.cpp
index e1b6baa..d4f58a9 100644
--- a/src/CommandFactory.cpp
+++ b/src/CommandFactory.cpp
@@ -22,6 +22,8 @@
#include "ConsoleMessages.h"
#include "RequestedUrl.h"
#include "CurrentUrl.h"
+#include "SetTimeout.h"
+#include "GetTimeout.h"
#include "ResizeWindow.h"
#include "IgnoreSslErrors.h"
#include "SetSkipImageLoading.h"
diff --git a/src/Connection.cpp b/src/Connection.cpp
index c00e977..90298f8 100644
--- a/src/Connection.cpp
+++ b/src/Connection.cpp
@@ -4,6 +4,7 @@
#include "CommandParser.h"
#include "CommandFactory.h"
#include "PageLoadingCommand.h"
+#include "TimeoutCommand.h"
#include "SocketCommand.h"
#include
@@ -24,18 +25,14 @@ Connection::Connection(QTcpSocket *socket, WebPageManager *manager, QObject *par
void Connection::commandReady(Command *command) {
m_queuedCommand = command;
m_manager->logger() << "Received" << command->toString();
- if (m_manager->isLoading()) {
- m_manager->logger() << command->toString() << "waiting for load to finish";
- m_commandWaiting = true;
- } else {
- startCommand();
- }
+ startCommand();
}
void Connection::startCommand() {
m_commandWaiting = false;
if (m_pageSuccess) {
m_runningCommand = new PageLoadingCommand(m_queuedCommand, m_manager, this);
+ m_runningCommand = new TimeoutCommand(m_runningCommand, m_manager, this);
connect(m_runningCommand, SIGNAL(finished(Response *)), this, SLOT(finishCommand(Response *)));
m_runningCommand->start();
} else {
@@ -45,9 +42,6 @@ void Connection::startCommand() {
void Connection::pendingLoadFinished(bool success) {
m_pageSuccess = m_pageSuccess && success;
- if (m_commandWaiting) {
- startCommand();
- }
}
void Connection::writePageLoadFailure() {
diff --git a/src/Connection.h b/src/Connection.h
index 244ddb6..59b3b70 100644
--- a/src/Connection.h
+++ b/src/Connection.h
@@ -31,7 +31,7 @@ class Connection : public QObject {
WebPageManager *m_manager;
CommandParser *m_commandParser;
CommandFactory *m_commandFactory;
- PageLoadingCommand *m_runningCommand;
+ Command *m_runningCommand;
bool m_pageSuccess;
bool m_commandWaiting;
WebPage *currentPage();
diff --git a/src/GetTimeout.cpp b/src/GetTimeout.cpp
new file mode 100644
index 0000000..844d441
--- /dev/null
+++ b/src/GetTimeout.cpp
@@ -0,0 +1,9 @@
+#include "GetTimeout.h"
+#include "WebPageManager.h"
+
+GetTimeout::GetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
+}
+
+void GetTimeout::start() {
+ emit finished(new Response(true, QString::number(manager()->getTimeout())));
+}
diff --git a/src/GetTimeout.h b/src/GetTimeout.h
new file mode 100644
index 0000000..1b96ab7
--- /dev/null
+++ b/src/GetTimeout.h
@@ -0,0 +1,11 @@
+#include "SocketCommand.h"
+
+class WebPageManager;
+
+class GetTimeout : public SocketCommand {
+ Q_OBJECT;
+
+ public:
+ GetTimeout(WebPageManager *page, QStringList &arguments, QObject *parent = 0);
+ virtual void start();
+};
diff --git a/src/SetTimeout.cpp b/src/SetTimeout.cpp
new file mode 100644
index 0000000..364cc49
--- /dev/null
+++ b/src/SetTimeout.cpp
@@ -0,0 +1,19 @@
+#include "SetTimeout.h"
+#include "WebPageManager.h"
+
+SetTimeout::SetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
+}
+
+void SetTimeout::start() {
+ QString timeoutString = arguments()[0];
+ bool ok;
+ int timeout = timeoutString.toInt(&ok);
+
+ if (ok) {
+ manager()->setTimeout(timeout);
+ emit finished(new Response(true));
+ } else {
+ emit finished(new Response(false, QString("Invalid value for timeout")));
+ }
+}
+
diff --git a/src/SetTimeout.h b/src/SetTimeout.h
new file mode 100644
index 0000000..390c117
--- /dev/null
+++ b/src/SetTimeout.h
@@ -0,0 +1,9 @@
+#include "SocketCommand.h"
+
+class SetTimeout : public SocketCommand {
+ Q_OBJECT
+
+ public:
+ SetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent = 0);
+ virtual void start();
+};
diff --git a/src/TimeoutCommand.cpp b/src/TimeoutCommand.cpp
new file mode 100644
index 0000000..ab44a8b
--- /dev/null
+++ b/src/TimeoutCommand.cpp
@@ -0,0 +1,59 @@
+#include "TimeoutCommand.h"
+#include "Command.h"
+#include "WebPageManager.h"
+#include "WebPage.h"
+#include
+
+TimeoutCommand::TimeoutCommand(Command *command, WebPageManager *manager, QObject *parent) : Command(parent) {
+ m_command = command;
+ m_manager = manager;
+ m_timer = new QTimer(this);
+ m_timer->setSingleShot(true);
+ connect(m_timer, SIGNAL(timeout()), this, SLOT(commandTimeout()));
+ connect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
+}
+
+void TimeoutCommand::start() {
+ if (m_manager->isLoading()) {
+ connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
+ startTimeout();
+ } else {
+ startCommand();
+ }
+}
+
+void TimeoutCommand::startCommand() {
+ connect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *)));
+ m_command->start();
+}
+
+void TimeoutCommand::startTimeout() {
+ int timeout = m_manager->getTimeout();
+ if (timeout > 0) {
+ m_timer->start(timeout * 1000);
+ }
+}
+
+void TimeoutCommand::pendingLoadFinished(bool success) {
+ if (success) {
+ startCommand();
+ } else {
+ emit finished(new Response(false, m_manager->currentPage()->failureString()));
+ }
+}
+
+void TimeoutCommand::pageLoadingFromCommand() {
+ startTimeout();
+}
+
+void TimeoutCommand::commandTimeout() {
+ m_manager->currentPage()->triggerAction(QWebPage::Stop);
+ m_command->deleteLater();
+ emit finished(new Response(false, QString("timeout")));
+}
+
+void TimeoutCommand::commandFinished(Response *response) {
+ m_command->deleteLater();
+ emit finished(response);
+}
+
diff --git a/src/TimeoutCommand.h b/src/TimeoutCommand.h
new file mode 100644
index 0000000..6ec4c2b
--- /dev/null
+++ b/src/TimeoutCommand.h
@@ -0,0 +1,41 @@
+#include "Command.h"
+#include
+#include
+
+class Response;
+class WebPageManager;
+class QTimer;
+
+/* Decorates a command with a timeout.
+ *
+ * If the timeout, using a QTimer is reached before
+ * the command is finished, the load page load will
+ * be stopped and failure response will be issued.
+ *
+ */
+class TimeoutCommand : public Command {
+ Q_OBJECT
+
+ public:
+ TimeoutCommand(Command *command, WebPageManager *page, QObject *parent = 0);
+ virtual void start();
+
+ public slots:
+ void commandTimeout();
+ void commandFinished(Response *response);
+ void pageLoadingFromCommand();
+ void pendingLoadFinished(bool);
+
+ signals:
+ void finished(Response *response);
+
+ protected:
+ void startCommand();
+ void startTimeout();
+
+ private:
+ WebPageManager *m_manager;
+ QTimer *m_timer;
+ Command *m_command;
+};
+
diff --git a/src/WebPageManager.cpp b/src/WebPageManager.cpp
index fcae551..d2ed1fa 100644
--- a/src/WebPageManager.cpp
+++ b/src/WebPageManager.cpp
@@ -8,6 +8,7 @@ WebPageManager::WebPageManager(QObject *parent) : QObject(parent) {
m_success = true;
m_loggingEnabled = false;
m_ignoredOutput = new QString();
+ m_timeout = -1;
createPage(this)->setFocus();
}
@@ -85,7 +86,16 @@ bool WebPageManager::ignoreSslErrors() {
return m_ignoreSslErrors;
}
+int WebPageManager::getTimeout() {
+ return m_timeout;
+}
+
+void WebPageManager::setTimeout(int timeout) {
+ m_timeout = timeout;
+}
+
void WebPageManager::reset() {
+ m_timeout = -1;
m_cookieJar->clearCookies();
m_pages.first()->deleteLater();
m_pages.clear();
diff --git a/src/WebPageManager.h b/src/WebPageManager.h
index b0002b0..b2f532c 100644
--- a/src/WebPageManager.h
+++ b/src/WebPageManager.h
@@ -22,6 +22,8 @@ class WebPageManager : public QObject {
WebPage *createPage(QObject *parent);
void setIgnoreSslErrors(bool);
bool ignoreSslErrors();
+ void setTimeout(int);
+ int getTimeout();
void reset();
NetworkCookieJar *cookieJar();
bool isLoading() const;
@@ -50,6 +52,7 @@ class WebPageManager : public QObject {
bool m_success;
bool m_loggingEnabled;
QString *m_ignoredOutput;
+ int m_timeout;
};
#endif // _WEBPAGEMANAGER_H
diff --git a/src/find_command.h b/src/find_command.h
index c74913b..a31cf60 100644
--- a/src/find_command.h
+++ b/src/find_command.h
@@ -39,3 +39,6 @@ CHECK_COMMAND(ClearPromptText)
CHECK_COMMAND(JavascriptAlertMessages)
CHECK_COMMAND(JavascriptConfirmMessages)
CHECK_COMMAND(JavascriptPromptMessages)
+CHECK_COMMAND(GetTimeout)
+CHECK_COMMAND(SetTimeout)
+
diff --git a/src/webkit_server.pro b/src/webkit_server.pro
index f6697dd..404c0e8 100644
--- a/src/webkit_server.pro
+++ b/src/webkit_server.pro
@@ -53,6 +53,9 @@ HEADERS = \
WindowFocus.h \
GetWindowHandles.h \
GetWindowHandle.h \
+ GetTimeout.h \
+ SetTimeout.h \
+ TimeoutCommand.h \
SOURCES = \
EnableLogging.cpp \
@@ -102,11 +105,14 @@ SOURCES = \
SetProxy.cpp \
NullCommand.cpp \
PageLoadingCommand.cpp \
+ SetTimeout.cpp \
+ GetTimeout.cpp \
SetSkipImageLoading.cpp \
WebPageManager.cpp \
WindowFocus.cpp \
GetWindowHandles.cpp \
GetWindowHandle.cpp \
+ TimeoutCommand.cpp \
RESOURCES = webkit_server.qrc
QT += network webkit