• 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 
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