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 35 class TreeSnapshotTaker : public WidgetVisitor { 36 public: TreeSnapshotTaker(string & receiver,vector<string> & leafNodes)37 explicit TreeSnapshotTaker(string &receiver, vector<string> &leafNodes) : receiver_(receiver), 38 leafNodes_(leafNodes) {}; 39 ~TreeSnapshotTaker()40 ~TreeSnapshotTaker() {} 41 Visit(const Widget & widget)42 void Visit(const Widget &widget) override 43 { 44 auto type = widget.GetAttr(ATTR_NAMES[UiAttr::TYPE], "") + "/"; 45 auto value = widget.GetAttr(ATTR_NAMES[UiAttr::TEXT], "") + "/"; 46 auto hashcode = widget.GetAttr(ATTR_NAMES[UiAttr::HASHCODE], "") + ";"; 47 receiver_ = receiver_ + type + value + hashcode; 48 if (value != "/") { 49 leafNodes_.push_back(type + value + hashcode); 50 } 51 } 52 53 private: 54 string &receiver_; 55 vector<string> &leafNodes_; 56 }; 57 TakeScopeUiSnapshot(const UiDriver & driver,const Widget & root,string & snapshot,vector<string> & leafNodes)58 static void TakeScopeUiSnapshot(const UiDriver &driver, const Widget &root, string &snapshot, 59 vector<string> &leafNodes) 60 { 61 TreeSnapshotTaker snapshotTaker(snapshot, leafNodes); 62 auto tree = driver.GetWidgetTree(); 63 if (tree != nullptr) { 64 tree->DfsTraverseDescendants(snapshotTaker, root); 65 } else { 66 LOG_W("WidgetTree is a nullptr currently"); 67 } 68 } 69 WidgetOperator(UiDriver & driver,const Widget & widget,const UiOpArgs & options)70 WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options) 71 : driver_(driver), widget_(widget), options_(options) 72 { 73 } 74 GenericClick(TouchOp op,ApiCallErr & error) const75 void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const 76 { 77 DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P); 78 auto retrieved = driver_.RetrieveWidget(widget_, error, true); 79 if (error.code_ != NO_ERROR) { 80 return; 81 } 82 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 83 auto touch = OHOS::uitest::GenericClick(op, center); 84 driver_.PerformTouch(touch, options_, error); 85 } 86 ScrollToEnd(bool toTop,ApiCallErr & error) const87 void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const 88 { 89 string prevSnapshot = ""; 90 string targetSnapshot = ""; 91 while (true) { 92 auto scrollWidget = driver_.RetrieveWidget(widget_, error); 93 if (scrollWidget == nullptr || error.code_ != NO_ERROR) { 94 return; 95 } 96 string snapshot; 97 vector<string> leafNodes; 98 TakeScopeUiSnapshot(driver_, *scrollWidget, snapshot, leafNodes); 99 if ((prevSnapshot == snapshot) || (snapshot.find(targetSnapshot) != string::npos && targetSnapshot != "")) { 100 return; 101 } 102 prevSnapshot = snapshot; 103 if (!leafNodes.empty()) { 104 targetSnapshot = (toTop ? leafNodes.front() : leafNodes.back()); 105 } else { 106 targetSnapshot = ""; 107 } 108 auto bounds = scrollWidget->GetBounds(); 109 if (options_.scrollWidgetDeadZone_ > 0) { 110 // scroll widget from its deadZone maybe unresponsive 111 bounds.top_ += options_.scrollWidgetDeadZone_; 112 bounds.bottom_ -= options_.scrollWidgetDeadZone_; 113 } 114 Point topPoint(bounds.GetCenterX(), bounds.top_); 115 Point bottomPoint(bounds.GetCenterX(), bounds.bottom_); 116 if (toTop) { 117 auto touch = GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint); 118 driver_.PerformTouch(touch, options_, error); 119 } else { 120 auto touch = GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 121 driver_.PerformTouch(touch, options_, error); 122 } 123 } 124 } 125 DragIntoWidget(const Widget & another,ApiCallErr & error) const126 void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const 127 { 128 auto widgetFrom = driver_.RetrieveWidget(widget_, error); 129 if (widgetFrom == nullptr || error.code_ != NO_ERROR) { 130 return; 131 } 132 auto widgetTo = driver_.RetrieveWidget(another, error, false); 133 if (widgetTo == nullptr || error.code_ != NO_ERROR) { 134 return; 135 } 136 auto boundsFrom = widgetFrom->GetBounds(); 137 auto boundsTo = widgetTo->GetBounds(); 138 auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY()); 139 auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY()); 140 auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo); 141 driver_.PerformTouch(touch, options_, error); 142 } 143 PinchWidget(float_t scale,ApiCallErr & error) const144 void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const 145 { 146 auto retrieved = driver_.RetrieveWidget(widget_, error); 147 if (retrieved == nullptr || error.code_ != NO_ERROR) { 148 return; 149 } 150 auto matcher = WidgetAttrMatcher(ATTR_NAMES[UiAttr::HIERARCHY], "ROOT", ValueMatchPattern::EQ); 151 auto selector = WidgetSelector(); 152 selector.AddMatcher(matcher); 153 vector<unique_ptr<Widget>> recv; 154 driver_.FindWidgets(selector, recv, error, false); 155 if (error.code_ != NO_ERROR) { 156 return; 157 } 158 if (recv.empty()) { 159 error = ApiCallErr(ERR_INTERNAL, "Cannot find root widget"); 160 return; 161 } 162 auto rootBound = recv.front()->GetBounds(); 163 auto rectBound = widget_.GetBounds(); 164 float_t widthScale = (float_t)(rootBound.GetWidth()) / (float_t)(rectBound.GetWidth()); 165 float_t heightScale = (float_t)(rootBound.GetHeight()) / (float_t)(rectBound.GetHeight()); 166 float_t originalScale = min(widthScale, heightScale); 167 if (scale < 0) { 168 error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale"); 169 return; 170 } else if (scale > originalScale) { 171 scale = originalScale; 172 } 173 auto touch = GenericPinch(rectBound, scale); 174 driver_.PerformTouch(touch, options_, error); 175 } InputText(string_view text,ApiCallErr & error) const176 void WidgetOperator::InputText(string_view text, ApiCallErr &error) const 177 { 178 auto retrieved = driver_.RetrieveWidget(widget_, error); 179 if (retrieved == nullptr || error.code_ != NO_ERROR) { 180 return; 181 } 182 auto origText = retrieved->GetAttr(ATTR_NAMES[UiAttr::TEXT], ""); 183 if (origText.empty() && text.empty()) { 184 return; 185 } 186 static constexpr uint32_t focusTimeMs = 500; 187 static constexpr uint32_t typeCharTimeMs = 50; 188 vector<KeyEvent> events; 189 if (!origText.empty()) { 190 for (size_t index = 0; index < origText.size(); index++) { 191 events.emplace_back(KeyEvent {ActionStage::DOWN, 2015, typeCharTimeMs}); 192 events.emplace_back(KeyEvent {ActionStage::UP, 2015, 0}); 193 events.emplace_back(KeyEvent {ActionStage::DOWN, 2055, typeCharTimeMs}); 194 events.emplace_back(KeyEvent {ActionStage::UP, 2055, 0}); 195 } 196 } 197 if (!text.empty()) { 198 vector<char> chars(text.begin(), text.end()); // decompose to sing-char input sequence 199 vector<pair<int32_t, int32_t>> keyCodes; 200 for (auto ch : chars) { 201 int32_t code = KEYCODE_NONE; 202 int32_t ctrlCode = KEYCODE_NONE; 203 if (!driver_.GetUiController(error)->GetCharKeyCode(ch, code, ctrlCode)) { 204 error = ApiCallErr(ERR_INVALID_INPUT, string("Cannot input char ") + ch); 205 return; 206 } 207 keyCodes.emplace_back(make_pair(code, ctrlCode)); 208 } 209 for (auto &pair : keyCodes) { 210 if (pair.second != KEYCODE_NONE) { 211 events.emplace_back(KeyEvent {ActionStage::DOWN, pair.second, 0}); 212 } 213 events.emplace_back(KeyEvent {ActionStage::DOWN, pair.first, typeCharTimeMs}); 214 events.emplace_back(KeyEvent {ActionStage::UP, pair.first, 0}); 215 if (pair.second != KEYCODE_NONE) { 216 events.emplace_back(KeyEvent {ActionStage::UP, pair.second, 0}); 217 } 218 } 219 } 220 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 221 auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center); 222 driver_.PerformTouch(touch, options_, error); 223 driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining 224 auto keyAction = KeysForwarder(events); 225 driver_.TriggerKey(keyAction, options_, error); 226 } 227 ScrollFindWidget(const WidgetSelector & selector,ApiCallErr & error) const228 unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const 229 { 230 bool scrollingUp = true; 231 string prevSnapshot = ""; 232 string targetSnapshot = ""; 233 vector<reference_wrapper<const Widget>> receiver; 234 while (true) { 235 auto scrollWidget = driver_.RetrieveWidget(widget_, error); 236 if (scrollWidget == nullptr || error.code_ != NO_ERROR) { 237 return nullptr; 238 } 239 selector.Select(*(driver_.GetWidgetTree()), receiver); 240 if (!receiver.empty()) { 241 auto& first = receiver.front().get(); 242 auto clone = first.Clone("NONE", first.GetHierarchy()); 243 // save the selection desc as dummy attribute 244 clone->SetAttr("selectionDesc", selector.Describe()); 245 return clone; 246 } 247 string snapshot; 248 vector<string> leafNodes; 249 TakeScopeUiSnapshot(driver_, *scrollWidget, snapshot, leafNodes); 250 if ((snapshot == prevSnapshot) || (snapshot.find(targetSnapshot) != string::npos && targetSnapshot != "")) { 251 // scrolling down to bottom, search completed with failure 252 if (!scrollingUp) { 253 auto msg = string("Scroll search widget failed: ") + selector.Describe(); 254 LOG_W("%{public}s", msg.c_str()); 255 return nullptr; 256 } else { 257 // scrolling down to top, change direction 258 scrollingUp = false; 259 } 260 } 261 prevSnapshot = snapshot; 262 if (!leafNodes.empty()) { 263 targetSnapshot = (scrollingUp ? leafNodes.front() : leafNodes.back()); 264 } else { 265 targetSnapshot = ""; 266 } 267 // execute scrolling on the scroll_widget without update UI 268 auto bounds = scrollWidget->GetBounds(); 269 if (options_.scrollWidgetDeadZone_ > 0) { 270 // scroll widget from its deadZone maybe unresponsive 271 bounds.top_ += options_.scrollWidgetDeadZone_; 272 bounds.bottom_ -= options_.scrollWidgetDeadZone_; 273 } 274 Point topPoint(bounds.GetCenterX(), bounds.top_); 275 Point bottomPoint(bounds.GetCenterX(), bounds.bottom_); 276 auto touch = (scrollingUp) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint) : 277 GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 278 driver_.PerformTouch(touch, options_, error); 279 } 280 } 281 } // namespace OHOS::uitest 282