From e677cb9c8e460ec7ca1611f397059740590d35ec Mon Sep 17 00:00:00 2001 From: Dan Ivovich Date: Tue, 10 Jul 2012 15:47:17 +0200 Subject: [PATCH] Control JavaScript confirmation dialogs from Ruby Adds #alert_messages, #confirm_messages, #prompt_messages, #accept_js_confirms!, #dismiss_js_confirms, #accept_js_prompts!, #dismiss_js_prompts, and #js_prompt_input= . --- README.md | 77 +++++++++++++ lib/capybara/webkit/browser.rb | 36 +++++++ lib/capybara/webkit/driver.rb | 36 +++++++ spec/driver_spec.rb | 174 ++++++++++++++++++++++++++++++ src/ClearPromptText.cpp | 11 ++ src/ClearPromptText.h | 9 ++ src/CommandFactory.cpp | 7 ++ src/JavascriptAlertMessages.cpp | 10 ++ src/JavascriptAlertMessages.h | 9 ++ src/JavascriptConfirmMessages.cpp | 10 ++ src/JavascriptConfirmMessages.h | 9 ++ src/JavascriptPromptMessages.cpp | 10 ++ src/JavascriptPromptMessages.h | 9 ++ src/SetConfirmAction.cpp | 11 ++ src/SetConfirmAction.h | 9 ++ src/SetPromptAction.cpp | 11 ++ src/SetPromptAction.h | 9 ++ src/SetPromptText.cpp | 11 ++ src/SetPromptText.h | 9 ++ src/WebPage.cpp | 46 ++++++-- src/WebPage.h | 12 +++ src/find_command.h | 7 ++ src/webkit_server.pro | 14 +++ 23 files changed, 540 insertions(+), 6 deletions(-) create mode 100644 src/ClearPromptText.cpp create mode 100644 src/ClearPromptText.h create mode 100644 src/JavascriptAlertMessages.cpp create mode 100644 src/JavascriptAlertMessages.h create mode 100644 src/JavascriptConfirmMessages.cpp create mode 100644 src/JavascriptConfirmMessages.h create mode 100644 src/JavascriptPromptMessages.cpp create mode 100644 src/JavascriptPromptMessages.h create mode 100644 src/SetConfirmAction.cpp create mode 100644 src/SetConfirmAction.h create mode 100644 src/SetPromptAction.cpp create mode 100644 src/SetPromptAction.h create mode 100644 src/SetPromptText.cpp create mode 100644 src/SetPromptText.h diff --git a/README.md b/README.md index 4958c70..620fa1c 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,20 @@ capybara-webkit supports a few methods that are not part of the standard capybar page.driver.error_messages => {:source=>"http://example.com", :line_number=>1, :message=>"SyntaxError: Parse error"} +**alert_messages, confirm_messages, prompt_messages**: returns arrays of Javascript dialog messages for each dialog type + + # In Javascript: + alert("HI"); + confirm("Ok?"); + prompt("Number?", "42"); + # In Ruby: + page.driver.alert_messages + => ["Hi"] + page.driver.confirm_messages + => ["Ok?"] + page.driver.prompt_messages + => ["Number?"] + **resize_window**: change the viewport size to the given width and height page.driver.resize_window(500, 300) @@ -92,6 +106,69 @@ capybara-webkit supports a few methods that are not part of the standard capybar page.driver.cookies["alpha"] => "abc" +**accept_js_confirms!**: accept any Javascript confirm that is triggered by the page's Javascript + + # In Javascript: + if (confirm("Ok?")) + console.log("Hi"); + else + console.log("Bye"); + # In Ruby: + page.driver.accept_js_confirms! + visit "/" + page.driver.console_messages.first[:message] + => "Hi" + +**dismiss_js_confirms!**: dismiss any Javascript confirm that is triggered by the page's Javascript + + # In Javascript: + if (confirm("Ok?")) + console.log("Hi"); + else + console.log("Bye"); + # In Ruby: + page.driver.dismiss_js_confirms! + visit "/" + page.driver.console_messages.first[:message] + => "Bye" + +**accept_js_prompts!**: accept any Javascript prompt that is triggered by the page's Javascript + + # In Javascript: + var a = prompt("Number?", "0") + console.log(a); + # In Ruby: + page.driver.accept_js_prompts! + visit "/" + page.driver.console_messages.first[:message] + => "0" + +**dismiss_js_prompts!**: dismiss any Javascript prompt that is triggered by the page's Javascript + + # In Javascript: + var a = prompt("Number?", "0") + if (a != null) + console.log(a); + else + console.log("you said no")); + # In Ruby: + page.driver.dismiss_js_prompts! + visit "/" + page.driver.console_messages.first[:message] + => "you said no" + +**js_prompt_input=(value)**: set the text to use if a Javascript prompt is encountered and accepted + + # In Javascript: + var a = prompt("Number?", "0") + console.log(a); + # In Ruby: + page.driver.js_prompt_input = "42" + page.driver.accept_js_prompts! + visit "/" + page.driver.console_messages.first[:message] + => "42" + Contributing ------------ diff --git a/lib/capybara/webkit/browser.rb b/lib/capybara/webkit/browser.rb index a67d644..0f33114 100644 --- a/lib/capybara/webkit/browser.rb +++ b/lib/capybara/webkit/browser.rb @@ -55,6 +55,18 @@ module Capybara::Webkit end end + def alert_messages + command("JavascriptAlertMessages").split("\n") + end + + def confirm_messages + command("JavascriptConfirmMessages").split("\n") + end + + def prompt_messages + command("JavascriptPromptMessages").split("\n") + end + def response_headers Hash[command("Headers").split("\n").map { |header| header.split(": ") }] end @@ -105,6 +117,30 @@ module Capybara::Webkit alias_method :window_handle, :get_window_handle + def accept_js_confirms + command("SetConfirmAction", "Yes") + end + + def reject_js_confirms + command("SetConfirmAction", "No") + end + + def accept_js_prompts + command("SetPromptAction", "Yes") + end + + def reject_js_prompts + command("SetPromptAction", "No") + end + + def set_prompt_text_to(string) + command("SetPromptText", string) + end + + def clear_prompt_text + command("ClearPromptText") + end + def command(name, *args) @connection.puts name @connection.puts args.size diff --git a/lib/capybara/webkit/driver.rb b/lib/capybara/webkit/driver.rb index f37122f..7d58cd1 100644 --- a/lib/capybara/webkit/driver.rb +++ b/lib/capybara/webkit/driver.rb @@ -68,6 +68,18 @@ module Capybara::Webkit browser.error_messages end + def alert_messages + browser.alert_messages + end + + def confirm_messages + browser.confirm_messages + end + + def prompt_messages + browser.prompt_messages + end + def response_headers browser.response_headers end @@ -107,6 +119,30 @@ module Capybara::Webkit browser.get_window_handle end + def accept_js_confirms! + browser.accept_js_confirms + end + + def dismiss_js_confirms! + browser.reject_js_confirms + end + + def accept_js_prompts! + browser.accept_js_prompts + end + + def dismiss_js_prompts! + browser.reject_js_prompts + end + + def js_prompt_input=(value) + if value.nil? + browser.clear_prompt_text + else + browser.set_prompt_text_to(value) + end + end + def wait? true end diff --git a/spec/driver_spec.rb b/spec/driver_spec.rb index e1bbfa7..c4c7569 100644 --- a/spec/driver_spec.rb +++ b/spec/driver_spec.rb @@ -403,6 +403,180 @@ describe Capybara::Webkit::Driver do driver.error_messages.length.should eq 1 end + it "empties the array when reset" do + driver.reset! + driver.console_messages.should be_empty + end + + end + + context "javascript dialog interaction" do + context "on an alert app" do + let(:driver) do + driver_for_html(<<-HTML) + + + + + + + + HTML + end + + before { driver.visit("/") } + + it "should let me read my alert messages" do + driver.alert_messages.first.should == "Alert Text Goes Here" + end + + it "empties the array when reset" do + driver.reset! + driver.alert_messages.should be_empty + end + end + + context "on a confirm app" do + let(:driver) do + driver_for_html(<<-HTML) + + + + + + + + + HTML + end + + before { driver.visit("/") } + + it "should default to accept the confirm" do + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello" + end + + it "can dismiss the confirm" do + driver.dismiss_js_confirms! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "goodbye" + end + + it "can accept the confirm explicitly" do + driver.dismiss_js_confirms! + driver.accept_js_confirms! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello" + end + + it "should collect the javsacript confirm dialog contents" do + driver.find("//input").first.click + driver.confirm_messages.first.should == "Yes?" + end + + it "empties the array when reset" do + driver.find("//input").first.click + driver.reset! + driver.confirm_messages.should be_empty + end + + it "resets to the default of accepting confirms" do + driver.dismiss_js_confirms! + driver.reset! + driver.visit("/") + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello" + end + end + + context "on a prompt app" do + let(:driver) do + driver_for_html(<<-HTML) + + + + + + + + + HTML + end + + before { driver.visit("/") } + + it "should default to dismiss the prompt" do + driver.find("//input").first.click + driver.console_messages.first[:message].should == "goodbye" + end + + it "can accept the prompt without providing text" do + driver.accept_js_prompts! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello John Smith" + end + + it "can accept the prompt with input" do + driver.js_prompt_input = "Capy" + driver.accept_js_prompts! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello Capy" + end + + it "can return to dismiss the prompt after accepting prompts" do + driver.accept_js_prompts! + driver.dismiss_js_prompts! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "goodbye" + end + + it "should let me remove the prompt input text" do + driver.js_prompt_input = "Capy" + driver.accept_js_prompts! + driver.find("//input").first.click + driver.console_messages.first[:message].should == "hello Capy" + driver.js_prompt_input = nil + driver.find("//input").first.click + driver.console_messages.last[:message].should == "hello John Smith" + end + + it "should collect the javsacript prompt dialog contents" do + driver.find("//input").first.click + driver.prompt_messages.first.should == "Your name?" + end + + it "empties the array when reset" do + driver.find("//input").first.click + driver.reset! + driver.prompt_messages.should be_empty + end + + it "returns the prompt action to dismiss on reset" do + driver.accept_js_prompts! + driver.reset! + driver.visit("/") + driver.find("//input").first.click + driver.console_messages.first[:message].should == "goodbye" + end + end end context "form app" do diff --git a/src/ClearPromptText.cpp b/src/ClearPromptText.cpp new file mode 100644 index 0000000..af403b7 --- /dev/null +++ b/src/ClearPromptText.cpp @@ -0,0 +1,11 @@ +#include "ClearPromptText.h" +#include "WebPage.h" +#include "WebPageManager.h" + +ClearPromptText::ClearPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void ClearPromptText::start() +{ + page()->setPromptText(QString()); + emit finished(new Response(true)); +} diff --git a/src/ClearPromptText.h b/src/ClearPromptText.h new file mode 100644 index 0000000..afc3244 --- /dev/null +++ b/src/ClearPromptText.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class ClearPromptText : public SocketCommand { + Q_OBJECT; + + public: + ClearPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/CommandFactory.cpp b/src/CommandFactory.cpp index 6713797..e1b6baa 100644 --- a/src/CommandFactory.cpp +++ b/src/CommandFactory.cpp @@ -31,6 +31,13 @@ #include "WebPageManager.h" #include "Authenticate.h" #include "EnableLogging.h" +#include "SetConfirmAction.h" +#include "SetPromptAction.h" +#include "SetPromptText.h" +#include "ClearPromptText.h" +#include "JavascriptAlertMessages.h" +#include "JavascriptConfirmMessages.h" +#include "JavascriptPromptMessages.h" CommandFactory::CommandFactory(WebPageManager *manager, QObject *parent) : QObject(parent) { m_manager = manager; diff --git a/src/JavascriptAlertMessages.cpp b/src/JavascriptAlertMessages.cpp new file mode 100644 index 0000000..30bc82f --- /dev/null +++ b/src/JavascriptAlertMessages.cpp @@ -0,0 +1,10 @@ +#include "JavascriptAlertMessages.h" +#include "WebPage.h" +#include "WebPageManager.h" + +JavascriptAlertMessages::JavascriptAlertMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void JavascriptAlertMessages::start() +{ + emit finished(new Response(true, page()->alertMessages())); +} diff --git a/src/JavascriptAlertMessages.h b/src/JavascriptAlertMessages.h new file mode 100644 index 0000000..4f275be --- /dev/null +++ b/src/JavascriptAlertMessages.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class JavascriptAlertMessages : public SocketCommand { + Q_OBJECT + + public: + JavascriptAlertMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/JavascriptConfirmMessages.cpp b/src/JavascriptConfirmMessages.cpp new file mode 100644 index 0000000..8e3ba67 --- /dev/null +++ b/src/JavascriptConfirmMessages.cpp @@ -0,0 +1,10 @@ +#include "JavascriptConfirmMessages.h" +#include "WebPage.h" +#include "WebPageManager.h" + +JavascriptConfirmMessages::JavascriptConfirmMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void JavascriptConfirmMessages::start() +{ + emit finished(new Response(true, page()->confirmMessages())); +} diff --git a/src/JavascriptConfirmMessages.h b/src/JavascriptConfirmMessages.h new file mode 100644 index 0000000..a088050 --- /dev/null +++ b/src/JavascriptConfirmMessages.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class JavascriptConfirmMessages : public SocketCommand { + Q_OBJECT + + public: + JavascriptConfirmMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/JavascriptPromptMessages.cpp b/src/JavascriptPromptMessages.cpp new file mode 100644 index 0000000..61621e5 --- /dev/null +++ b/src/JavascriptPromptMessages.cpp @@ -0,0 +1,10 @@ +#include "JavascriptPromptMessages.h" +#include "WebPage.h" +#include "WebPageManager.h" + +JavascriptPromptMessages::JavascriptPromptMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void JavascriptPromptMessages::start() +{ + emit finished(new Response(true, page()->promptMessages())); +} diff --git a/src/JavascriptPromptMessages.h b/src/JavascriptPromptMessages.h new file mode 100644 index 0000000..b9e3e6d --- /dev/null +++ b/src/JavascriptPromptMessages.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class JavascriptPromptMessages : public SocketCommand { + Q_OBJECT + + public: + JavascriptPromptMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/SetConfirmAction.cpp b/src/SetConfirmAction.cpp new file mode 100644 index 0000000..28706bc --- /dev/null +++ b/src/SetConfirmAction.cpp @@ -0,0 +1,11 @@ +#include "SetConfirmAction.h" +#include "WebPage.h" +#include "WebPageManager.h" + +SetConfirmAction::SetConfirmAction(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void SetConfirmAction::start() +{ + page()->setConfirmAction(arguments()[0]); + emit finished(new Response(true)); +} diff --git a/src/SetConfirmAction.h b/src/SetConfirmAction.h new file mode 100644 index 0000000..7e0469a --- /dev/null +++ b/src/SetConfirmAction.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class SetConfirmAction : public SocketCommand { + Q_OBJECT; + + public: + SetConfirmAction(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/SetPromptAction.cpp b/src/SetPromptAction.cpp new file mode 100644 index 0000000..f2256c9 --- /dev/null +++ b/src/SetPromptAction.cpp @@ -0,0 +1,11 @@ +#include "SetPromptAction.h" +#include "WebPage.h" +#include "WebPageManager.h" + +SetPromptAction::SetPromptAction(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void SetPromptAction::start() +{ + page()->setPromptAction(arguments()[0]); + emit finished(new Response(true)); +} diff --git a/src/SetPromptAction.h b/src/SetPromptAction.h new file mode 100644 index 0000000..24239de --- /dev/null +++ b/src/SetPromptAction.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class SetPromptAction : public SocketCommand { + Q_OBJECT; + + public: + SetPromptAction(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/SetPromptText.cpp b/src/SetPromptText.cpp new file mode 100644 index 0000000..3992739 --- /dev/null +++ b/src/SetPromptText.cpp @@ -0,0 +1,11 @@ +#include "SetPromptText.h" +#include "WebPage.h" +#include "WebPageManager.h" + +SetPromptText::SetPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} + +void SetPromptText::start() +{ + page()->setPromptText(arguments()[0]); + emit finished(new Response(true)); +} diff --git a/src/SetPromptText.h b/src/SetPromptText.h new file mode 100644 index 0000000..67467dd --- /dev/null +++ b/src/SetPromptText.h @@ -0,0 +1,9 @@ +#include "SocketCommand.h" + +class SetPromptText : public SocketCommand { + Q_OBJECT; + + public: + SetPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); + virtual void start(); +}; diff --git a/src/WebPage.cpp b/src/WebPage.cpp index ad9b32d..d24403c 100644 --- a/src/WebPage.cpp +++ b/src/WebPage.cpp @@ -20,6 +20,9 @@ WebPage::WebPage(WebPageManager *manager, QObject *parent) : QWebPage(parent) { loadJavascript(); setUserStylesheet(); + m_confirm = true; + m_prompt = false; + m_prompt_text = QString(); this->setCustomNetworkAccessManager(); connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted())); @@ -99,6 +102,18 @@ QString WebPage::consoleMessages() { return m_consoleMessages.join("\n"); } +QString WebPage::alertMessages() { + return m_alertMessages.join("\n"); +} + +QString WebPage::confirmMessages() { + return m_confirmMessages.join("\n"); +} + +QString WebPage::promptMessages() { + return m_promptMessages.join("\n"); +} + void WebPage::setUserAgent(QString userAgent) { m_userAgent = userAgent; } @@ -140,21 +155,27 @@ void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, c void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) { Q_UNUSED(frame); + m_alertMessages.append(message); std::cout << "ALERT: " << qPrintable(message) << std::endl; } bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) { Q_UNUSED(frame); - Q_UNUSED(message); - return true; + m_confirmMessages.append(message); + return m_confirm; } bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) { Q_UNUSED(frame) - Q_UNUSED(message) - Q_UNUSED(defaultValue) - Q_UNUSED(result) - return false; + m_promptMessages.append(message); + if (m_prompt) { + if (m_prompt_text.isNull()) { + *result = defaultValue; + } else { + *result = m_prompt_text; + } + } + return m_prompt; } void WebPage::loadStarted() { @@ -297,3 +318,16 @@ bool WebPage::matchesWindowSelector(QString selector) { void WebPage::setFocus() { m_manager->setCurrentPage(this); } + +void WebPage::setConfirmAction(QString action) { + m_confirm = (action == "Yes"); +} + +void WebPage::setPromptAction(QString action) { + m_prompt = (action == "Yes"); +} + +void WebPage::setPromptText(QString text) { + m_prompt_text = text; +} + diff --git a/src/WebPage.h b/src/WebPage.h index 2cbd4ff..e171f14 100644 --- a/src/WebPage.h +++ b/src/WebPage.h @@ -14,12 +14,18 @@ class WebPage : public QWebPage { QString failureString(); QString userAgentForUrl(const QUrl &url ) const; void setUserAgent(QString userAgent); + void setConfirmAction(QString action); + void setPromptAction(QString action); + void setPromptText(QString action); int getLastStatus(); void setCustomNetworkAccessManager(); bool render(const QString &fileName); virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0); void setSkipImageLoading(bool skip); QString consoleMessages(); + QString alertMessages(); + QString confirmMessages(); + QString promptMessages(); void resetWindowSize(); QWebPage *createWindow(WebWindowType type); QString uuid(); @@ -63,7 +69,13 @@ class WebPage : public QWebPage { void setUserStylesheet(); int m_lastStatus; QString m_pageHeaders; + bool m_confirm; + bool m_prompt; QStringList m_consoleMessages; + QStringList m_alertMessages; + QStringList m_confirmMessages; + QString m_prompt_text; + QStringList m_promptMessages; QString m_uuid; WebPageManager *m_manager; QString m_errorPageMessage; diff --git a/src/find_command.h b/src/find_command.h index a939c00..c74913b 100644 --- a/src/find_command.h +++ b/src/find_command.h @@ -32,3 +32,10 @@ CHECK_COMMAND(GetWindowHandles) CHECK_COMMAND(GetWindowHandle) CHECK_COMMAND(Authenticate) CHECK_COMMAND(EnableLogging) +CHECK_COMMAND(SetConfirmAction) +CHECK_COMMAND(SetPromptAction) +CHECK_COMMAND(SetPromptText) +CHECK_COMMAND(ClearPromptText) +CHECK_COMMAND(JavascriptAlertMessages) +CHECK_COMMAND(JavascriptConfirmMessages) +CHECK_COMMAND(JavascriptPromptMessages) diff --git a/src/webkit_server.pro b/src/webkit_server.pro index 79478e4..f6697dd 100644 --- a/src/webkit_server.pro +++ b/src/webkit_server.pro @@ -4,6 +4,13 @@ DESTDIR = . HEADERS = \ EnableLogging.h \ Authenticate.h \ + SetConfirmAction.h \ + SetPromptAction.h \ + SetPromptText.h \ + ClearPromptText.h \ + JavascriptAlertMessages.h \ + JavascriptConfirmMessages.h \ + JavascriptPromptMessages.h \ IgnoreSslErrors.h \ ResizeWindow.h \ CurrentUrl.h \ @@ -50,6 +57,13 @@ HEADERS = \ SOURCES = \ EnableLogging.cpp \ Authenticate.cpp \ + SetConfirmAction.cpp \ + SetPromptAction.cpp \ + SetPromptText.cpp \ + ClearPromptText.cpp \ + JavascriptAlertMessages.cpp \ + JavascriptConfirmMessages.cpp \ + JavascriptPromptMessages.cpp \ IgnoreSslErrors.cpp \ ResizeWindow.cpp \ CurrentUrl.cpp \