1 /* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #include "widget_operator.h" 17 18 namespace OHOS::uitest { 19 using namespace std; 20 using namespace nlohmann; 21 22 class KeysForwarder : public KeyAction { 23 public: KeysForwarder(const vector<KeyEvent> & evetns)24 explicit KeysForwarder(const vector<KeyEvent> &evetns) : events_(evetns) {}; 25 ComputeEvents(vector<KeyEvent> & recv,const UiOpArgs & opt) const26 void ComputeEvents(vector<KeyEvent> &recv, const UiOpArgs &opt) const override 27 { 28 recv = events_; 29 } 30 31 private: 32 const vector<KeyEvent> &events_; 33 }; 34 WidgetOperator(UiDriver & driver,const Widget & widget,const UiOpArgs & options)35 WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options) 36 : driver_(driver), widget_(widget), options_(options) 37 { 38 } 39 GenericClick(TouchOp op,ApiCallErr & error) const40 void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const 41 { 42 DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P); 43 auto retrieved = driver_.RetrieveWidget(widget_, error, true); 44 if (error.code_ != NO_ERROR) { 45 return; 46 } 47 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 48 auto touch = OHOS::uitest::GenericClick(op, center); 49 driver_.PerformTouch(touch, options_, error); 50 } 51 ScrollToEnd(bool toTop,ApiCallErr & error) const52 void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const 53 { 54 while (true) { 55 auto scrollWidget = driver_.RetrieveWidget(widget_, error); 56 if (scrollWidget == nullptr || error.code_ != NO_ERROR) { 57 return; 58 } 59 vector<string> visibleNodes; 60 vector<string> allNodes; 61 TreeSnapshotTaker snapshotTaker(visibleNodes, allNodes); 62 driver_.DfsTraverseTree(snapshotTaker, scrollWidget); 63 if (visibleNodes.empty() && allNodes.empty()) { 64 return; 65 } 66 auto mark1 = toTop ? visibleNodes.front() : visibleNodes.back(); 67 auto mark2 = toTop ? allNodes.front() : allNodes.back(); 68 if (mark1 == mark2) { 69 return; 70 } 71 auto bounds = scrollWidget->GetBounds(); 72 if (options_.scrollWidgetDeadZone_ > 0) { 73 // scroll widget from its deadZone maybe unresponsive 74 if (bounds.top_ + options_.scrollWidgetDeadZone_ >= bounds.bottom_ - options_.scrollWidgetDeadZone_) { 75 bounds.top_ += (bounds.bottom_ - bounds.top_) / INDEX_FOUR; 76 bounds.bottom_ -= (bounds.bottom_ - bounds.top_) / INDEX_FOUR; 77 } else { 78 bounds.top_ += options_.scrollWidgetDeadZone_; 79 bounds.bottom_ -= options_.scrollWidgetDeadZone_; 80 } 81 } 82 Point topPoint(bounds.GetCenterX(), bounds.top_); 83 Point bottomPoint(bounds.GetCenterX(), bounds.bottom_); 84 if (toTop) { 85 auto touch = GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint); 86 driver_.PerformTouch(touch, options_, error); 87 } else { 88 auto touch = GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 89 driver_.PerformTouch(touch, options_, error); 90 } 91 } 92 } 93 DragIntoWidget(const Widget & another,ApiCallErr & error) const94 void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const 95 { 96 auto widgetFrom = driver_.RetrieveWidget(widget_, error); 97 if (widgetFrom == nullptr || error.code_ != NO_ERROR) { 98 return; 99 } 100 auto widgetTo = driver_.RetrieveWidget(another, error, false); 101 if (widgetTo == nullptr || error.code_ != NO_ERROR) { 102 return; 103 } 104 auto boundsFrom = widgetFrom->GetBounds(); 105 auto boundsTo = widgetTo->GetBounds(); 106 auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY()); 107 auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY()); 108 auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo); 109 driver_.PerformTouch(touch, options_, error); 110 } 111 PinchWidget(float_t scale,ApiCallErr & error) const112 void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const 113 { 114 auto retrieved = driver_.RetrieveWidget(widget_, error); 115 if (retrieved == nullptr || error.code_ != NO_ERROR) { 116 return; 117 } 118 auto matcher = WidgetAttrMatcher(ATTR_NAMES[UiAttr::HIERARCHY], "ROOT", ValueMatchPattern::EQ); 119 auto selector = WidgetSelector(); 120 selector.AddMatcher(matcher); 121 vector<unique_ptr<Widget>> recv; 122 driver_.FindWidgets(selector, recv, error, false); 123 if (error.code_ != NO_ERROR) { 124 return; 125 } 126 if (recv.empty()) { 127 error = ApiCallErr(ERR_INTERNAL, "Cannot find root widget"); 128 return; 129 } 130 auto rootBound = recv.front()->GetBounds(); 131 auto rectBound = widget_.GetBounds(); 132 float_t widthScale = (float_t)(rootBound.GetWidth()) / (float_t)(rectBound.GetWidth()); 133 float_t heightScale = (float_t)(rootBound.GetHeight()) / (float_t)(rectBound.GetHeight()); 134 float_t originalScale = min(widthScale, heightScale); 135 if (scale < 0) { 136 error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale"); 137 return; 138 } else if (scale > originalScale) { 139 scale = originalScale; 140 } 141 auto touch = GenericPinch(rectBound, scale); 142 driver_.PerformTouch(touch, options_, error); 143 } 144 TextToKeyAction(string_view text,std::vector<KeyEvent> & events,UiDriver & driver,ApiCallErr & error)145 static bool TextToKeyAction(string_view text, std::vector<KeyEvent> &events, UiDriver &driver, ApiCallErr &error) 146 { 147 static constexpr uint32_t typeCharTimeMs = 50; 148 if (!text.empty()) { 149 vector<char> chars(text.begin(), text.end()); // decompose to sing-char input sequence 150 vector<pair<int32_t, int32_t>> keyCodes; 151 for (auto ch : chars) { 152 int32_t code = KEYCODE_NONE; 153 int32_t ctrlCode = KEYCODE_NONE; 154 if (!driver.GetCharKeyCode(ch, code, ctrlCode, error)) { 155 return false; 156 } 157 keyCodes.emplace_back(make_pair(code, ctrlCode)); 158 } 159 for (auto &pair : keyCodes) { 160 if (pair.second != KEYCODE_NONE) { 161 events.emplace_back(KeyEvent {ActionStage::DOWN, pair.second, 0}); 162 } 163 events.emplace_back(KeyEvent {ActionStage::DOWN, pair.first, typeCharTimeMs}); 164 events.emplace_back(KeyEvent {ActionStage::UP, pair.first, 0}); 165 if (pair.second != KEYCODE_NONE) { 166 events.emplace_back(KeyEvent {ActionStage::UP, pair.second, 0}); 167 } 168 } 169 } 170 return true; 171 } 172 InputText(string_view text,ApiCallErr & error) const173 void WidgetOperator::InputText(string_view text, ApiCallErr &error) const 174 { 175 auto retrieved = driver_.RetrieveWidget(widget_, error); 176 if (retrieved == nullptr || error.code_ != NO_ERROR) { 177 return; 178 } 179 auto origText = retrieved->GetAttr(ATTR_NAMES[UiAttr::TEXT], ""); 180 if (origText.empty() && text.empty()) { 181 return; 182 } 183 static constexpr uint32_t focusTimeMs = 500; 184 static constexpr uint32_t typeCharTimeMs = 50; 185 vector<KeyEvent> events; 186 if (!origText.empty()) { 187 for (size_t index = 0; index < origText.size(); index++) { 188 events.emplace_back(KeyEvent {ActionStage::DOWN, KEYCODE_DPAD_RIGHT, typeCharTimeMs}); 189 events.emplace_back(KeyEvent {ActionStage::UP, KEYCODE_DPAD_RIGHT, 0}); 190 events.emplace_back(KeyEvent {ActionStage::DOWN, KEYCODE_DEL, typeCharTimeMs}); 191 events.emplace_back(KeyEvent {ActionStage::UP, KEYCODE_DEL, 0}); 192 } 193 } 194 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 195 auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center); 196 driver_.PerformTouch(touch, options_, error); 197 driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining 198 auto keyActionForDelete = KeysForwarder(events); 199 driver_.TriggerKey(keyActionForDelete, options_, error); 200 events.clear(); 201 if (!text.empty()) { 202 if (TextToKeyAction(text, events, driver_, error)) { 203 LOG_I("inputText by Keycode"); 204 auto keyActionForInput = KeysForwarder(events); 205 driver_.TriggerKey(keyActionForInput, options_, error); 206 } else { 207 LOG_I("inputText by pasteBoard"); 208 auto actionForPatse = CombinedKeys(KEYCODE_CTRL, KEYCODE_V, KEYCODE_NONE); 209 driver_.TriggerKey(actionForPatse, options_, error); 210 } 211 } 212 } 213 ScrollFindWidget(const WidgetSelector & selector,ApiCallErr & error) const214 unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const 215 { 216 bool scrollingUp = true; 217 vector<unique_ptr<Widget>> receiver; 218 while (true) { 219 auto scrollWidget = driver_.RetrieveWidget(widget_, error); 220 if (scrollWidget == nullptr || error.code_ != NO_ERROR) { 221 return nullptr; 222 } 223 driver_.FindWidgets(selector, receiver, error, false); 224 if (!receiver.empty()) { 225 auto first = receiver.begin(); 226 auto clone = (*first)->Clone("NONE", (*first)->GetHierarchy()); 227 // save the selection desc as dummy attribute 228 clone->SetAttr("selectionDesc", selector.Describe()); 229 return clone; 230 } 231 vector<string> visibleNodes; 232 vector<string> allNodes; 233 TreeSnapshotTaker snapshotTaker(visibleNodes, allNodes); 234 driver_.DfsTraverseTree(snapshotTaker, scrollWidget); 235 if (visibleNodes.empty() && allNodes.empty()) { 236 return nullptr; 237 } 238 auto mark1 = scrollingUp ? visibleNodes.front() : visibleNodes.back(); 239 auto mark2 = scrollingUp ? allNodes.front() : allNodes.back(); 240 if (mark1 == mark2) { 241 // scrolling down to bottom, search completed with failure 242 if (!scrollingUp) { 243 LOG_W("Scroll search widget failed: %{public}s", selector.Describe().data()); 244 return nullptr; 245 } else { 246 // scrolling down to top, change direction 247 scrollingUp = false; 248 } 249 } 250 // execute scrolling on the scroll_widget without update UI 251 auto bounds = scrollWidget->GetBounds(); 252 if (options_.scrollWidgetDeadZone_ > 0) { 253 // scroll widget from its deadZone maybe unresponsive 254 if (bounds.top_ + options_.scrollWidgetDeadZone_ >= bounds.bottom_ - options_.scrollWidgetDeadZone_) { 255 bounds.top_ += (bounds.bottom_ - bounds.top_) / INDEX_FOUR; 256 bounds.bottom_ -= (bounds.bottom_ - bounds.top_) / INDEX_FOUR; 257 } else { 258 bounds.top_ += options_.scrollWidgetDeadZone_; 259 bounds.bottom_ -= options_.scrollWidgetDeadZone_; 260 } 261 } 262 Point topPoint(bounds.GetCenterX(), bounds.top_); 263 Point bottomPoint(bounds.GetCenterX(), bounds.bottom_); 264 auto touch = (scrollingUp) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint) : 265 GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 266 driver_.PerformTouch(touch, options_, error); 267 } 268 } 269 } // namespace OHOS::uitest 270