• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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