使用appium输入中文,发现好慢!至少5秒以上,如果在这样的情况下做测试,这就好悲剧了。
从appium(1.6.3)代码上来看,没有什么问题,直接是通过boostrap的setText的方法。说是就下载了appium-bootstrap的代码看,从这里开发找到的代码,都是java的代码,找到 io.appium.android.bootstrap.handler.SetText
在new Clear().execute(command);时间长达5秒(打日志发现),不管文本框有没有内容,都会执行
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.appium.android.bootstrap.handler;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.*;
import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
import io.appium.android.bootstrap.handler.Find;
import org.json.JSONException;
import java.util.Hashtable;
/**
* This handler is used to set text in elements that support it.
*
*/
public class SetText extends CommandHandler {
/*
* @param command The {@link AndroidCommand} used for this handler.
*
* @return {@link AndroidCommandResult}
*
* @throws JSONException
*
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
AndroidElement el = null;
if (command.isElementCommand()) {
el = command.getElement();
Logger.debug("Using element passed in: " + el.getId());
} else {
try {
AndroidElementsHash elements = AndroidElementsHash.getInstance();
el = elements.getElement(new UiSelector().focused(true), "");
Logger.debug("Using currently-focused element: " + el.getId());
} catch (ElementNotFoundException e) {
Logger.debug("Error retrieving focused element: " + e);
return getErrorResult("Unable to set text without a focused element.");
}
}
try {
final Hashtable<String, Object> params = command.params();
boolean replace = Boolean.parseBoolean(params.get("replace").toString());
String text = params.get("text").toString();
boolean pressEnter = false;
if (text.endsWith("\\n")) {
pressEnter = true;
text = text.replace("\\n", "");
Logger.debug("Will press enter after setting text");
}
boolean unicodeKeyboard = false;
if (params.get("unicodeKeyboard") != null) {
unicodeKeyboard = Boolean.parseBoolean(params.get("unicodeKeyboard").toString());
}
String currText = el.getText();
new Clear().execute(command); //不管有没有,这里都会执行
if (!el.getText().isEmpty()) {
// clear could have failed, or we could have a hint in the field
// we'll assume it is the latter
Logger.debug("Text not cleared. Assuming remainder is hint text.");
currText = "";
}
if (!replace) {
text = currText + text;
}
final boolean result = el.setText(text, unicodeKeyboard);
if (!result) {
return getErrorResult("el.setText() failed!");
}
if (pressEnter) {
final UiDevice d = UiDevice.getInstance();
d.pressEnter();
}
return getSuccessResult(result);
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final Exception e) { // handle NullPointerException
return getErrorResult("Unknown error");
}
}
}
然后,我们再看Clear的代码
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.appium.android.bootstrap.handler;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.AndroidElement;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.WDStatus;
import io.appium.uiautomator.core.InteractionController;
import io.appium.uiautomator.core.UiAutomatorBridge;
import org.json.JSONException;
import java.lang.reflect.InvocationTargetException;
/**
* This handler is used to clear elements in the Android UI.
*
* Based on the element Id, clear that element.
*
* UiAutomator method clearText is flaky hence overriding it with custom implementation.
*/
public class Clear extends CommandHandler {
/*
* Trying to select entire text with correctLongClick and increasing time intervals.
* Checking if element still has text in them and and if true falling back on UiAutomator clearText
*
* @param command The {@link AndroidCommand}
*
* @return {@link AndroidCommandResult}
*
* @throws JSONException
*
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
if (command.isElementCommand()) {
try {
final AndroidElement el = command.getElement();
// first, try to do native clearing
Logger.debug("Attempting to clear using UiObject.clearText().");
el.clearText(); //无条件都会执行这块。然后再分析clearText
if (el.getText().isEmpty()) {
return getSuccessResult(true);
}
// see if there is hint text
if (hasHintText(el)) {
Logger.debug("Text remains after clearing, "
+ "but it appears to be hint text.");
return getSuccessResult(true);
}
// next try to select everything and delete
Logger.debug("Clearing text not successful. Attempting to clear " +
"by selecting all and deleting.");
if (selectAndDelete(el)) {
return getSuccessResult(true);
}
// see if there is hint text
if (hasHintText(el)) {
Logger.debug("Text remains after clearing, "
+ "but it appears to be hint text.");
return getSuccessResult(true);
}
// finally try to send delete keys
Logger.debug("Clearing text not successful. Attempting to clear " +
"by sending delete keys.");
if (sendDeleteKeys(el)) {
return getSuccessResult(true);
}
if (!el.getText().isEmpty()) {
// either there was a failure, or there is hint text
if (hasHintText(el)) {
Logger.debug("Text remains after clearing, " +
"but it appears to be hint text.");
return getSuccessResult(true);
} else if (!el.getText().isEmpty()) {
Logger.debug("Exhausted all means to clear text but '" +
el.getText() + "' remains.");
return getErrorResult("Clear text not successful.");
}
}
return getSuccessResult(true);
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final Exception e) { // handle NullPointerException
return getErrorResult("Unknown error clearing text");
}
}
return getErrorResult("Unknown error");
}
private boolean selectAndDelete(AndroidElement el)
throws UiObjectNotFoundException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Rect rect = el.getVisibleBounds();
// Trying to select entire text.
TouchLongClick.correctLongClick(rect.left + 20, rect.centerY(), 2000);
UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
if (selectAll.waitForExists(2000)) {
selectAll.click();
}
// wait for the selection
SystemClock.sleep(500);
// delete it
UiAutomatorBridge.getInstance().getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
return el.getText().isEmpty();
}
private boolean sendDeleteKeys(AndroidElement el)
throws UiObjectNotFoundException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
String tempTextHolder = "";
// Preventing infinite while loop.
while (!el.getText().isEmpty() && !tempTextHolder.equalsIgnoreCase(el.getText())) {
// Trying send delete keys after clicking in text box.
el.click();
// Sending delete keys asynchronously, both forward and backward
for (int key : new int[] { KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_FORWARD_DEL }) {
tempTextHolder = el.getText();
final int length = tempTextHolder.length();
final long eventTime = SystemClock.uptimeMillis();
KeyEvent deleteEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
key, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
for (int count = 0; count < length; count++) {
UiAutomatorBridge.getInstance().injectInputEvent(deleteEvent, false);
}
}
}
return el.getText().isEmpty();
}
private boolean hasHintText(AndroidElement el)
throws UiObjectNotFoundException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
// to test if the remaining text is hint text, try sending a single
// delete key and testing if there is any change.
// ignore the off-chance that the delete silently fails and we get a false
// positive.
String currText = el.getText();
try {
if (!el.getBoolAttribute("focused")) {
Logger.debug("Could not check for hint text because the element is not focused!");
return false;
}
} catch (final Exception e) {
Logger.debug("Could not check for hint text: " + e.getMessage());
return false;
}
InteractionController interactionController = UiAutomatorBridge.getInstance().getInteractionController();
interactionController.sendKey(KeyEvent.KEYCODE_DEL, 0);
interactionController.sendKey(KeyEvent.KEYCODE_FORWARD_DEL, 0);
return currText.equals(el.getText());
}
}
再看看AndroidElement.clearText是什么样的
public void clearText() throws UiObjectNotFoundException {
el.clearTextField();
}
这个都就是com.android.uiautomator.core.UiObject.clearTextField
于是找再找到uiautomator的代码再来分析(这个代码需要下载andriod sdk,在对应android版本的目录下,会有源码,也有uiautomator的源代码),我这里的路径是:
Android\sdk\sources\android-19\com\android\uiautomator\core
在UiObject.java找到clearTextField实现
/**
* Clears the existing text contents in an editable field.
*
* The {@link UiSelector} of this object must reference a UI element that is editable.
*
* When you call this method, the method first sets focus at the start edge of the field.
* The method then simulates a long-press to select the existing text, and deletes the
* selected text.
*
* If a "Select-All" option is displayed, the method will automatically attempt to use it
* to ensure full text selection.
*
* Note that it is possible that not all the text in the field is selected; for example,
* if the text contains separators such as spaces, slashes, at symbol etc.
* Also, not all editable fields support the long-press functionality.
*
* @throws UiObjectNotFoundException
* @since API Level 16
*/
public void clearTextField() throws UiObjectNotFoundException {
Tracer.trace();
// long click left + center
AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
if(node == null) {
throw new UiObjectNotFoundException(getSelector().toString());
}
Rect rect = getVisibleBounds(node);
getInteractionController().longTapNoSync(rect.left + 20, rect.centerY()); //长按
// check if the edit menu is open
UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
if(selectAll.waitForExists(50))
selectAll.click();
// wait for the selection
SystemClock.sleep(250); //这里等250ms
// delete it
getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
}
相信大家,可以找到慢的原因了。这里做一次长按,然再再做全选,然后再sleep(250),还有一个selectAll.waitForExists(50), 这些都是耗费时间的。
再找一下UiObject.java中setText的实现
public boolean setText(String text) throws UiObjectNotFoundException {
Tracer.trace(text);
clearTextField();
return getInteractionController().sendText(text);
}
发现这里又调用了一次clearTextField,这样算来,设一次文本,都会清理两次文本,于是,这时间就长了。
优化:只需要将io.appium.android.bootstrap.handler.SetText中的new Clear().execute(command)去掉就可以了。