Support most of the keys specified by Capybara for Node#send_keys
This commit is contained in:
parent
702bce9ce9
commit
b6fdc65bf1
|
@ -48,20 +48,9 @@ module Capybara::Webkit
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_keys(*keys)
|
def send_keys(*keys)
|
||||||
invoke("sendKeys", keys.map { |key|
|
# Currently unsupported keys specified by Capybara
|
||||||
case key
|
# :separator
|
||||||
when :space
|
invoke("sendKeys", convert_to_named_keys(keys).to_json)
|
||||||
" "
|
|
||||||
when :enter
|
|
||||||
"\r"
|
|
||||||
when :backspace
|
|
||||||
"\b"
|
|
||||||
when String
|
|
||||||
key.to_s
|
|
||||||
else
|
|
||||||
raise Capybara::NotSupportedByDriverError.new, "Unrecognized key(s) in #{key}"
|
|
||||||
end
|
|
||||||
}.join)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_option
|
def select_option
|
||||||
|
@ -172,5 +161,45 @@ module Capybara::Webkit
|
||||||
def ==(other)
|
def ==(other)
|
||||||
invoke("equals", other.native) == "true"
|
invoke("equals", other.native) == "true"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def convert_to_named_keys(key)
|
||||||
|
if key.is_a? Array
|
||||||
|
key.map {|k| convert_to_named_keys(k)}
|
||||||
|
else
|
||||||
|
case key
|
||||||
|
when :cancel, :help, :backspace, :tab, :clear, :return, :enter, :insert, :delete, :pause, :escape,
|
||||||
|
:space, :end, :home, :left, :up, :right, :down, :semicolon,
|
||||||
|
:f1, :f2, :f3, :f4, :f5, :f6, :f7, :f8, :f9, :f10, :f11, :f12,
|
||||||
|
:shift, :control, :alt, :meta
|
||||||
|
{ "key" => key.to_s.capitalize }
|
||||||
|
when :equals
|
||||||
|
{ "key" => "Equal" }
|
||||||
|
when :page_up
|
||||||
|
{ "key" => "PageUp" }
|
||||||
|
when :page_down
|
||||||
|
{ "key" => "PageDown" }
|
||||||
|
when :numpad0, :numpad1, :numpad2, :numpad3, :numpad4, :numpad5, :numpad6, :numpad7, :numpad9, :numpad9
|
||||||
|
{ "key" => key[-1], "modifier" => 'Keypad' }
|
||||||
|
when :multiply
|
||||||
|
{ "key" => "Asterisk", "modifier" => 'Keypad' }
|
||||||
|
when :divide
|
||||||
|
{ "key" => "Slash", "modifier" => 'Keypad' }
|
||||||
|
when :add
|
||||||
|
{ "key" => "Plus", "modifier" => 'Keypad' }
|
||||||
|
when :subtract
|
||||||
|
{ "key" => "Minus", "modifier" => 'Keypad' }
|
||||||
|
when :decimal
|
||||||
|
{"key" => "Period", "modifier" => 'Keypad'}
|
||||||
|
when :command
|
||||||
|
{ "key" => "Meta" }
|
||||||
|
when String
|
||||||
|
key.to_s
|
||||||
|
else
|
||||||
|
raise Capybara::NotSupportedByDriverError.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1273,6 +1273,17 @@ describe Capybara::Webkit::Driver do
|
||||||
<input type="radio" id="only-radio" value="1"/>
|
<input type="radio" id="only-radio" value="1"/>
|
||||||
<button type="reset">Reset Form</button>
|
<button type="reset">Reset Form</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="key_events"></div>
|
||||||
|
<script>
|
||||||
|
var form = document.getElementsByTagName('form')[0];
|
||||||
|
var output = document.getElementById('key_events');
|
||||||
|
form.addEventListener('keydown', function(e){
|
||||||
|
output.innerHTML = output.innerHTML + " d:" + (e.key || e.which);
|
||||||
|
});
|
||||||
|
form.addEventListener('keyup', function(e){
|
||||||
|
output.innerHTML = output.innerHTML + " u:" + (e.key || e.which);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body></html>
|
</body></html>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
@ -1341,6 +1352,20 @@ describe Capybara::Webkit::Driver do
|
||||||
input.send_keys(*[:backspace])
|
input.send_keys(*[:backspace])
|
||||||
expect(input.value).to eq "do"
|
expect(input.value).to eq "do"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should support :modifiers" do
|
||||||
|
input = driver.find_xpath("//input").first
|
||||||
|
input.send_keys("abc", [:shift, :left], "def")
|
||||||
|
expect(input.value).to eq "abdef"
|
||||||
|
input.send_keys([:control, "a"], [:shift, "upper"])
|
||||||
|
expect(input.value).to eq "UPPER"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "releases modifiers correctly" do
|
||||||
|
input = driver.find_xpath("//input").first
|
||||||
|
input.send_keys("a", [:shift, :left], "a")
|
||||||
|
expect(driver.find_css("#key_events").first.text).to eq "d:65 u:65 d:16 d:37 u:37 u:16 d:65 u:65"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:monkey_option) { driver.find_xpath("//option[@id='select-option-monkey']").first }
|
let(:monkey_option) { driver.find_xpath("//option[@id='select-option-monkey']").first }
|
||||||
|
|
|
@ -52,11 +52,9 @@ RSpec.configure do |c|
|
||||||
# Accessing unattached nodes is allowed when reload is disabled - Legacy behavior
|
# Accessing unattached nodes is allowed when reload is disabled - Legacy behavior
|
||||||
# Node#send_keys does not support modifiers and only supports a subset of special keys
|
# Node#send_keys does not support modifiers and only supports a subset of special keys
|
||||||
c.filter_run_excluding :full_description => lambda { |description, metadata|
|
c.filter_run_excluding :full_description => lambda { |description, metadata|
|
||||||
(description !~ /Capybara::Session webkit node #send_keys should send a string of keys to an element/) && (
|
(description =~ /Capybara::Session webkit node #reload without automatic reload should not automatically reload/ ||
|
||||||
description =~ /Capybara::Session webkit node #send_keys/ ||
|
|
||||||
description =~ /Capybara::Session webkit node #reload without automatic reload should not automatically reload/ ||
|
|
||||||
if Gem::Version.new(Capybara::VERSION) < Gem::Version.new("2.12.0")
|
if Gem::Version.new(Capybara::VERSION) < Gem::Version.new("2.12.0")
|
||||||
description =~ /Capybara::Session webkit Capybara::Window\s*#(size|resize_to|maximize|close.*no_such_window_error|send_keys)/ ||
|
description =~ /Capybara::Session webkit Capybara::Window\s*#(size|resize_to|maximize|close.*no_such_window_error)/ ||
|
||||||
description =~ /Capybara::Session webkit node\s*#set should allow me to change the contents of a contenteditable elements child/
|
description =~ /Capybara::Session webkit node\s*#set should allow me to change the contents of a contenteditable elements child/
|
||||||
else
|
else
|
||||||
description =~ /Capybara::Session webkit Capybara::Window\s*#close.*no_such_window_error/
|
description =~ /Capybara::Session webkit Capybara::Window\s*#close.*no_such_window_error/
|
||||||
|
|
|
@ -134,14 +134,79 @@ int JavascriptInvocation::keyCodeFor(const QChar &key) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int JavascriptInvocation::keyCodeForName(const QString &keyName) {
|
||||||
|
const QMetaObject &mo = JavascriptInvocation::staticMetaObject;
|
||||||
|
int prop_index = mo.indexOfProperty("key_enum");
|
||||||
|
QMetaProperty metaProperty = mo.property(prop_index);
|
||||||
|
QMetaEnum metaEnum = metaProperty.enumerator();
|
||||||
|
|
||||||
|
QByteArray array ((QString("Key_") + keyName).toStdString().c_str());
|
||||||
|
return metaEnum.keyToValue(array);
|
||||||
|
// return Qt::Key_unknown;
|
||||||
|
}
|
||||||
|
|
||||||
void JavascriptInvocation::keypress(QChar key) {
|
void JavascriptInvocation::keypress(QChar key) {
|
||||||
int keyCode = keyCodeFor(key);
|
int keyCode = keyCodeFor(key);
|
||||||
QKeyEvent event(QKeyEvent::KeyPress, keyCode, Qt::NoModifier, key);
|
QKeyEvent event(QKeyEvent::KeyPress, keyCode, m_currentModifiers, (m_currentModifiers ? QString() : key));
|
||||||
QApplication::sendEvent(m_page, &event);
|
QApplication::sendEvent(m_page, &event);
|
||||||
event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, Qt::NoModifier, key);
|
event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, m_currentModifiers);
|
||||||
QApplication::sendEvent(m_page, &event);
|
QApplication::sendEvent(m_page, &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JavascriptInvocation::namedKeypress(QString keyName, QString modifiers){
|
||||||
|
Qt::KeyboardModifiers key_modifiers(m_currentModifiers);
|
||||||
|
if (modifiers == "Keypad") {
|
||||||
|
key_modifiers |= Qt::KeypadModifier;
|
||||||
|
};
|
||||||
|
int keyCode = keyCodeForName(keyName);
|
||||||
|
QKeyEvent event(QKeyEvent::KeyPress, keyCode, key_modifiers);
|
||||||
|
QApplication::sendEvent(m_page, &event);
|
||||||
|
event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, key_modifiers);
|
||||||
|
QApplication::sendEvent(m_page, &event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JavascriptInvocation::namedKeydown(QString keyName){
|
||||||
|
int keyCode = keyCodeForName(keyName);
|
||||||
|
QKeyEvent event(QKeyEvent::KeyPress, keyCode, m_currentModifiers);
|
||||||
|
QApplication::sendEvent(m_page, &event);
|
||||||
|
|
||||||
|
switch(keyCode){
|
||||||
|
case Qt::Key_Shift:
|
||||||
|
m_currentModifiers |= Qt::ShiftModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Control:
|
||||||
|
m_currentModifiers |= Qt::ControlModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Alt:
|
||||||
|
m_currentModifiers |= Qt::AltModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Meta:
|
||||||
|
m_currentModifiers |= Qt::MetaModifier;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void JavascriptInvocation::namedKeyup(QString keyName){
|
||||||
|
int keyCode = keyCodeForName(keyName);
|
||||||
|
QKeyEvent event(QKeyEvent::KeyRelease, keyCode, m_currentModifiers, 0);
|
||||||
|
QApplication::sendEvent(m_page, &event);
|
||||||
|
|
||||||
|
switch(keyCode){
|
||||||
|
case Qt::Key_Shift:
|
||||||
|
m_currentModifiers &= ~Qt::ShiftModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Control:
|
||||||
|
m_currentModifiers &= ~Qt::ControlModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Alt:
|
||||||
|
m_currentModifiers &= ~Qt::AltModifier;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Meta:
|
||||||
|
m_currentModifiers &= ~Qt::MetaModifier;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const QString JavascriptInvocation::render(void) {
|
const QString JavascriptInvocation::render(void) {
|
||||||
QString pathTemplate =
|
QString pathTemplate =
|
||||||
QDir::temp().absoluteFilePath("./click_failed_XXXXXX.png");
|
QDir::temp().absoluteFilePath("./click_failed_XXXXXX.png");
|
||||||
|
|
|
@ -13,6 +13,7 @@ class JavascriptInvocation : public QObject {
|
||||||
Q_PROPERTY(bool allowUnattached READ allowUnattached)
|
Q_PROPERTY(bool allowUnattached READ allowUnattached)
|
||||||
Q_PROPERTY(QStringList arguments READ arguments)
|
Q_PROPERTY(QStringList arguments READ arguments)
|
||||||
Q_PROPERTY(QVariant error READ getError WRITE setError)
|
Q_PROPERTY(QVariant error READ getError WRITE setError)
|
||||||
|
Q_PROPERTY(Qt::Key key_enum)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent = 0);
|
JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent = 0);
|
||||||
|
@ -26,6 +27,9 @@ class JavascriptInvocation : public QObject {
|
||||||
Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height);
|
Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height);
|
||||||
Q_INVOKABLE void hover(int absoluteX, int absoluteY);
|
Q_INVOKABLE void hover(int absoluteX, int absoluteY);
|
||||||
Q_INVOKABLE void keypress(QChar);
|
Q_INVOKABLE void keypress(QChar);
|
||||||
|
Q_INVOKABLE void namedKeydown(QString keyName);
|
||||||
|
Q_INVOKABLE void namedKeyup(QString keyName);
|
||||||
|
Q_INVOKABLE void namedKeypress(QString keyName, QString modifiers);
|
||||||
Q_INVOKABLE const QString render(void);
|
Q_INVOKABLE const QString render(void);
|
||||||
QVariant getError();
|
QVariant getError();
|
||||||
void setError(QVariant error);
|
void setError(QVariant error);
|
||||||
|
@ -39,5 +43,8 @@ class JavascriptInvocation : public QObject {
|
||||||
QVariant m_error;
|
QVariant m_error;
|
||||||
void hover(const QPoint &);
|
void hover(const QPoint &);
|
||||||
int keyCodeFor(const QChar &);
|
int keyCodeFor(const QChar &);
|
||||||
|
int keyCodeForName(const QString &);
|
||||||
|
Qt::Key key_enum;
|
||||||
|
Qt::KeyboardModifiers m_currentModifiers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ Capybara = {
|
||||||
nextIndex: 0,
|
nextIndex: 0,
|
||||||
nodes: {},
|
nodes: {},
|
||||||
attachedFiles: [],
|
attachedFiles: [],
|
||||||
|
keyModifiersStack: [],
|
||||||
|
|
||||||
invoke: function () {
|
invoke: function () {
|
||||||
try {
|
try {
|
||||||
|
@ -285,17 +286,45 @@ Capybara = {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
sendKeys: function (index, keys) {
|
sendKeys: function (elem_index, json_keys) {
|
||||||
var strindex, length;
|
var idx, length, keys;
|
||||||
|
keys = JSON.parse(json_keys);
|
||||||
length = keys.length;
|
length = keys.length;
|
||||||
|
|
||||||
if (length) {
|
if (length) {
|
||||||
this.focus(index);
|
this.focus(elem_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (strindex = 0; strindex < length; strindex++) {
|
for (idx = 0; idx < length; idx++) {
|
||||||
CapybaraInvocation.keypress(keys[strindex]);
|
this._sendKeys(keys[idx]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_sendKeys: function(keys) {
|
||||||
|
if (typeof keys == "string") {
|
||||||
|
var str_len = keys.length;
|
||||||
|
var str_idx;
|
||||||
|
for (str_idx = 0; str_idx < str_len; str_idx++) {
|
||||||
|
CapybaraInvocation.keypress(keys[str_idx]);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(keys)) {
|
||||||
|
this.keyModifiersStack.push([]);
|
||||||
|
var idx;
|
||||||
|
for (idx = 0; idx < keys.length; idx++) {
|
||||||
|
this._sendKeys(keys[idx]);
|
||||||
|
}
|
||||||
|
var mods = this.keyModifiersStack.pop();
|
||||||
|
while (mods.length) {
|
||||||
|
CapybaraInvocation.namedKeyup(mods.pop().key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key = keys.key
|
||||||
|
if (["Shift", "Control", "Alt", "Meta"].indexOf(key) > -1){
|
||||||
|
CapybaraInvocation.namedKeydown(key);
|
||||||
|
this.keyModifiersStack[this.keyModifiersStack.length-1].push(keys);
|
||||||
|
} else {
|
||||||
|
CapybaraInvocation.namedKeypress(key, keys.modifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue