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
|
||||
|
||||
def send_keys(*keys)
|
||||
invoke("sendKeys", keys.map { |key|
|
||||
case key
|
||||
when :space
|
||||
" "
|
||||
when :enter
|
||||
"\r"
|
||||
when :backspace
|
||||
"\b"
|
||||
when String
|
||||
key.to_s
|
||||
else
|
||||
raise Capybara::NotSupportedByDriverError.new, "Unrecognized key(s) in #{key}"
|
||||
end
|
||||
}.join)
|
||||
# Currently unsupported keys specified by Capybara
|
||||
# :separator
|
||||
invoke("sendKeys", convert_to_named_keys(keys).to_json)
|
||||
end
|
||||
|
||||
def select_option
|
||||
|
@ -172,5 +161,45 @@ module Capybara::Webkit
|
|||
def ==(other)
|
||||
invoke("equals", other.native) == "true"
|
||||
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
|
||||
|
|
|
@ -1273,6 +1273,17 @@ describe Capybara::Webkit::Driver do
|
|||
<input type="radio" id="only-radio" value="1"/>
|
||||
<button type="reset">Reset Form</button>
|
||||
</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>
|
||||
HTML
|
||||
end
|
||||
|
@ -1341,6 +1352,20 @@ describe Capybara::Webkit::Driver do
|
|||
input.send_keys(*[:backspace])
|
||||
expect(input.value).to eq "do"
|
||||
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
|
||||
|
||||
let(:monkey_option) { driver.find_xpath("//option[@id='select-option-monkey']").first }
|
||||
|
|
|
@ -52,15 +52,13 @@ RSpec.configure do |c|
|
|||
# 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
|
||||
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 #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")
|
||||
description =~ /Capybara::Session webkit Capybara::Window\s*#(size|resize_to|maximize|close.*no_such_window_error|send_keys)/ ||
|
||||
description =~ /Capybara::Session webkit node\s*#set should allow me to change the contents of a contenteditable elements child/
|
||||
else
|
||||
description =~ /Capybara::Session webkit Capybara::Window\s*#close.*no_such_window_error/
|
||||
end
|
||||
(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")
|
||||
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/
|
||||
else
|
||||
description =~ /Capybara::Session webkit Capybara::Window\s*#close.*no_such_window_error/
|
||||
end
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, Qt::NoModifier, key);
|
||||
event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, m_currentModifiers);
|
||||
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) {
|
||||
QString pathTemplate =
|
||||
QDir::temp().absoluteFilePath("./click_failed_XXXXXX.png");
|
||||
|
|
|
@ -13,6 +13,7 @@ class JavascriptInvocation : public QObject {
|
|||
Q_PROPERTY(bool allowUnattached READ allowUnattached)
|
||||
Q_PROPERTY(QStringList arguments READ arguments)
|
||||
Q_PROPERTY(QVariant error READ getError WRITE setError)
|
||||
Q_PROPERTY(Qt::Key key_enum)
|
||||
|
||||
public:
|
||||
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 void hover(int absoluteX, int absoluteY);
|
||||
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);
|
||||
QVariant getError();
|
||||
void setError(QVariant error);
|
||||
|
@ -39,5 +43,8 @@ class JavascriptInvocation : public QObject {
|
|||
QVariant m_error;
|
||||
void hover(const QPoint &);
|
||||
int keyCodeFor(const QChar &);
|
||||
int keyCodeForName(const QString &);
|
||||
Qt::Key key_enum;
|
||||
Qt::KeyboardModifiers m_currentModifiers;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ Capybara = {
|
|||
nextIndex: 0,
|
||||
nodes: {},
|
||||
attachedFiles: [],
|
||||
keyModifiersStack: [],
|
||||
|
||||
invoke: function () {
|
||||
try {
|
||||
|
@ -285,17 +286,45 @@ Capybara = {
|
|||
return true;
|
||||
},
|
||||
|
||||
sendKeys: function (index, keys) {
|
||||
var strindex, length;
|
||||
|
||||
sendKeys: function (elem_index, json_keys) {
|
||||
var idx, length, keys;
|
||||
keys = JSON.parse(json_keys);
|
||||
length = keys.length;
|
||||
|
||||
if (length) {
|
||||
this.focus(index);
|
||||
this.focus(elem_index);
|
||||
}
|
||||
|
||||
for (strindex = 0; strindex < length; strindex++) {
|
||||
CapybaraInvocation.keypress(keys[strindex]);
|
||||
for (idx = 0; idx < length; idx++) {
|
||||
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