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