1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include <GTUtilsMdi.h>
23 #include <primitives/GTTabWidget.h>
24 #include <primitives/GTWidget.h>
25 
26 #include <QRegularExpression>
27 #include <QStringBuilder>
28 #include <QTabWidget>
29 
30 #include <U2Gui/HoverQLabel.h>
31 
32 #include "GTUtilsDashboard.h"
33 
34 namespace U2 {
35 using namespace HI;
36 
37 const QString GTUtilsDashboard::TREE_ROOT_ID = ExternalToolsDashboardWidget::TREE_ID;
38 
toString() const39 QString GTUtilsDashboard::Notification::toString() const {
40     return "[" % type.toUpper() % "][" % element % "] " % message;
41 }
42 
43 #define GT_CLASS_NAME "GTUtilsDashboard"
44 
45 #define GT_METHOD_NAME "getCopyButton"
getCopyButton(GUITestOpStatus & os,const QString & toolRunNodeId)46 QWidget *GTUtilsDashboard::getCopyButton(GUITestOpStatus &os, const QString &toolRunNodeId) {
47     auto node = getExternalToolNode(os, toolRunNodeId);
48     return GTWidget::findWidget(os, "copyButton", node);
49 }
50 #undef GT_METHOD_NAME
51 
52 #define GT_METHOD_NAME "getExternalToolsWidget"
getExternalToolsWidget(GUITestOpStatus & os)53 ExternalToolsDashboardWidget *GTUtilsDashboard::getExternalToolsWidget(GUITestOpStatus &os) {
54     Dashboard *dashboard = getDashboard(os);
55     return GTWidget::findWidgetByType<ExternalToolsDashboardWidget *>(os, dashboard, "External tools widget is not found");
56 }
57 #undef GT_METHOD_NAME
58 
59 #define GT_METHOD_NAME "getExternalToolNode"
getExternalToolNode(GUITestOpStatus & os,const QString & nodeId)60 ExternalToolsTreeNode *GTUtilsDashboard::getExternalToolNode(GUITestOpStatus &os, const QString &nodeId) {
61     ExternalToolsDashboardWidget *widget = getExternalToolsWidget(os);
62     ExternalToolsTreeNode *node = qobject_cast<ExternalToolsTreeNode *>(GTWidget::findWidget(os, nodeId, widget));
63     GT_CHECK_RESULT(node != nullptr, "External tool node not found: " + nodeId, nullptr);
64     return node;
65 }
66 #undef GT_METHOD_NAME
67 
68 #define GT_METHOD_NAME "getExternalToolNodeByText"
getExternalToolNodeByText(GUITestOpStatus & os,const QString & textPattern,bool isExactMatch)69 ExternalToolsTreeNode *GTUtilsDashboard::getExternalToolNodeByText(GUITestOpStatus &os, const QString &textPattern, bool isExactMatch) {
70     return getExternalToolNodeByText(os, nullptr, textPattern, isExactMatch);
71 }
72 #undef GT_METHOD_NAME
73 
74 #define GT_METHOD_NAME "getExternalToolNodesByText"
getExternalToolNodesByText(HI::GUITestOpStatus & os,ExternalToolsTreeNode * parent,const QString & textPattern,bool isExactMatch)75 QList<ExternalToolsTreeNode *> GTUtilsDashboard::getExternalToolNodesByText(HI::GUITestOpStatus &os, ExternalToolsTreeNode *parent, const QString &textPattern, bool isExactMatch) {
76     QList<ExternalToolsTreeNode *> nodes = parent == nullptr ? getExternalToolsWidget(os)->findChildren<ExternalToolsTreeNode *>() : parent->children;
77     QList<ExternalToolsTreeNode *> result;
78     for (auto node : qAsConst(nodes)) {
79         if (node->content == textPattern) {
80             result << node;
81         } else if (!isExactMatch && node->content.contains(textPattern)) {
82             result << node;
83         }
84     }
85     return result;
86 }
87 #undef GT_METHOD_NAME
88 
89 #define GT_METHOD_NAME "getExternalToolNodeByTextWithParent"
getExternalToolNodeByText(GUITestOpStatus & os,ExternalToolsTreeNode * parent,const QString & textPattern,bool isExactMatch)90 ExternalToolsTreeNode *GTUtilsDashboard::getExternalToolNodeByText(GUITestOpStatus &os, ExternalToolsTreeNode *parent, const QString &textPattern, bool isExactMatch) {
91     QList<ExternalToolsTreeNode *> nodes = parent == nullptr ? getExternalToolsWidget(os)->findChildren<ExternalToolsTreeNode *>() : parent->children;
92     for (auto node : qAsConst(nodes)) {
93         if (node->content == textPattern) {
94             return node;
95         } else if (!isExactMatch && node->content.contains(textPattern)) {
96             return node;
97         }
98     }
99     GT_CHECK_RESULT(false, "External tool node by text not found: " + textPattern, nullptr);
100 }
101 #undef GT_METHOD_NAME
102 
getTabWidget(HI::GUITestOpStatus & os)103 QTabWidget *GTUtilsDashboard::getTabWidget(HI::GUITestOpStatus &os) {
104     return GTWidget::findExactWidget<QTabWidget *>(os, "WorkflowTabView", GTUtilsMdi::activeWindow(os));
105 }
106 
findLoadSchemaButton(HI::GUITestOpStatus & os)107 QToolButton *GTUtilsDashboard::findLoadSchemaButton(HI::GUITestOpStatus &os) {
108     Dashboard *dashboard = findDashboard(os);
109     return dashboard == nullptr ? nullptr : dashboard->findChild<QToolButton *>("loadSchemaButton");
110 }
111 
getDashboardName(GUITestOpStatus & os,int dashboardNumber)112 const QString GTUtilsDashboard::getDashboardName(GUITestOpStatus &os, int dashboardNumber) {
113     return GTTabWidget::getTabName(os, getTabWidget(os), dashboardNumber);
114 }
115 
getFileButtonLabels(QWidget * parentWidget)116 static QStringList getFileButtonLabels(QWidget *parentWidget) {
117     QList<QToolButton *> buttons = parentWidget->findChildren<QToolButton *>();
118     QStringList labels;
119     for (auto button : qAsConst(buttons)) {
120         labels << button->text();
121     }
122     return labels;
123 }
124 
getInputFiles(HI::GUITestOpStatus & os)125 QStringList GTUtilsDashboard::getInputFiles(HI::GUITestOpStatus &os) {
126     openTab(os, Input);
127     auto dashboard = getDashboard(os);
128     auto parametersWidget = GTWidget::findWidget(os, "ParametersDashboardWidget", dashboard);
129     return getFileButtonLabels(parametersWidget);
130 }
131 
getOutputFiles(HI::GUITestOpStatus & os)132 QStringList GTUtilsDashboard::getOutputFiles(HI::GUITestOpStatus &os) {
133     auto dashboard = getDashboard(os);
134     auto outputFilesWidget = GTWidget::findWidget(os, "OutputFilesDashboardWidget", dashboard);
135     return getFileButtonLabels(outputFilesWidget);
136 }
137 
138 #define GT_METHOD_NAME "clickOutputFile"
clickOutputFile(GUITestOpStatus & os,const QString & outputFileName)139 void GTUtilsDashboard::clickOutputFile(GUITestOpStatus &os, const QString &outputFileName) {
140     auto dashboard = getDashboard(os);
141     auto outputFilesWidget = GTWidget::findWidget(os, "OutputFilesDashboardWidget", dashboard);
142     auto button = GTWidget::findButtonByText(os, outputFileName, outputFilesWidget);
143     GTWidget::click(os, button);
144 }
145 #undef GT_METHOD_NAME
146 
hasNotifications(HI::GUITestOpStatus & os)147 bool GTUtilsDashboard::hasNotifications(HI::GUITestOpStatus &os) {
148     openTab(os, Overview);
149     auto dashboard = getDashboard(os);
150     auto notificationsWidget = GTWidget::findWidget(os, "NotificationsDashboardWidget", dashboard);
151     return notificationsWidget->isVisible();
152 }
153 
154 #define GT_METHOD_NAME "getNotificationTypeFromHtml"
getNotificationTypeFromHtml(HI::GUITestOpStatus & os,const QString & html)155 QString GTUtilsDashboard::getNotificationTypeFromHtml(HI::GUITestOpStatus &os, const QString &html) {
156     QString type;
157 
158     int start = html.indexOf("<img class=\"");
159     int end = html.indexOf("\"", start + 12);  // 12 = length of "<img class=\""
160     GT_CHECK_RESULT(start >= 0 && end >= 0, "Dashboard notification type not found", type)
161 
162     start += 12;
163     end -= start;
164     type = html.mid(start, end);
165     return type;
166 }
167 #undef GT_METHOD_NAME
168 
169 #define GT_METHOD_NAME "getNotificationCellText"
getNotificationCellText(HI::GUITestOpStatus & os,const QGridLayout & tableLayout,const int row,const int col)170 QString GTUtilsDashboard::getNotificationCellText(HI::GUITestOpStatus &os, const QGridLayout &tableLayout, const int row, const int col) {
171     const QWidget *cellWidget = tableLayout.itemAtPosition(row, col)->widget();
172     QString text;
173 
174     if (cellWidget != nullptr && cellWidget->objectName() == "tableCell") {
175         if (const QLayout *const cellLayout = cellWidget->layout()) {
176             for (int labelInd = 0; labelInd < cellLayout->count(); ++labelInd) {
177                 if (const auto *const label = qobject_cast<QLabel *>(cellLayout->itemAt(labelInd)->widget())) {
178                     text = label->text();
179                 }
180             }
181         }
182     }
183     GT_CHECK_RESULT(!text.isEmpty(),
184                     QString("Error getting (%1,%2) cell of dashboard notification table").arg(row).arg(col),
185                     text)
186     return text;
187 }
188 #undef GT_METHOD_NAME
189 
190 #define GT_METHOD_NAME "getNotifications"
getNotifications(GUITestOpStatus & os)191 QList<GTUtilsDashboard::Notification> GTUtilsDashboard::getNotifications(GUITestOpStatus &os) {
192     const QString notificationsWidgetName = "NotificationsDashboardWidget";
193     QWidget *const notificationsWidget = GTWidget::findWidget(os, notificationsWidgetName, GTUtilsDashboard::getDashboard(os));
194     const auto tableLayout = qobject_cast<QGridLayout *>(notificationsWidget->layout());
195     QList<Notification> notifications;
196 
197     GT_CHECK_RESULT(tableLayout != nullptr && tableLayout->columnCount() == 3,
198                     notificationsWidgetName % " was found, but cannot be used in a test",
199                     notifications)
200 
201     const int notificationsNumber = tableLayout->rowCount();
202     for (int row = 1; row < notificationsNumber; row++) {
203         const QString type = getNotificationTypeFromHtml(os, getNotificationCellText(os, *tableLayout, row, 0));
204         const QString element = getNotificationCellText(os, *tableLayout, row, 1);
205         const QString message = getNotificationCellText(os, *tableLayout, row, 2);
206         notifications << Notification {type, element, message};
207     }
208     return notifications;
209 }
210 #undef GT_METHOD_NAME
211 
getJoinedNotificationsString(GUITestOpStatus & os)212 QString GTUtilsDashboard::getJoinedNotificationsString(GUITestOpStatus &os) {
213     const auto notifications = getNotifications(os);
214     QStringList stringNotifications;
215     for (const auto &notification : qAsConst(notifications)) {
216         stringNotifications << notification.toString();
217     }
218     return stringNotifications.join('\n');
219 }
220 
getTabObjectName(Tabs tab)221 QString GTUtilsDashboard::getTabObjectName(Tabs tab) {
222     switch (tab) {
223         case Overview:
224             return "overviewTabButton";
225         case Input:
226             return "inputTabButton";
227         case ExternalTools:
228             return "externalToolsTabButton";
229     }
230     return "unknown tab";
231 }
232 
233 #define GT_METHOD_NAME "findDashboard"
findDashboard(HI::GUITestOpStatus & os)234 Dashboard *GTUtilsDashboard::findDashboard(HI::GUITestOpStatus &os) {
235     QTabWidget *tabWidget = getTabWidget(os);
236     return tabWidget == nullptr ? nullptr : qobject_cast<Dashboard *>(tabWidget->currentWidget());
237 }
238 #undef GT_METHOD_NAME
239 
240 #define GT_METHOD_NAME "getDashboard"
getDashboard(HI::GUITestOpStatus & os)241 Dashboard *GTUtilsDashboard::getDashboard(HI::GUITestOpStatus &os) {
242     auto dashboard = findDashboard(os);
243     GT_CHECK_RESULT(dashboard != nullptr, "Dashboard widget not found", nullptr);
244     return dashboard;
245 }
246 #undef GT_METHOD_NAME
247 
248 #define GT_METHOD_NAME "openTab"
openTab(HI::GUITestOpStatus & os,Tabs tab)249 void GTUtilsDashboard::openTab(HI::GUITestOpStatus &os, Tabs tab) {
250     QWidget *dashboard = findDashboard(os);
251     GT_CHECK(dashboard != nullptr, "Dashboard widget not found");
252 
253     QString tabButtonObjectName = getTabObjectName(tab);
254     QToolButton *tabButton = GTWidget::findExactWidget<QToolButton *>(os, tabButtonObjectName, dashboard);
255     GT_CHECK(tabButton != nullptr, "Tab button not found: " + tabButtonObjectName);
256 
257     GTWidget::click(os, tabButton);
258 }
259 #undef GT_METHOD_NAME
260 
261 #define GT_METHOD_NAME "hasTab"
hasTab(HI::GUITestOpStatus & os,Tabs tab)262 bool GTUtilsDashboard::hasTab(HI::GUITestOpStatus &os, Tabs tab) {
263     QWidget *dashboard = findDashboard(os);
264     GT_CHECK_RESULT(dashboard != nullptr, "Dashboard is not found", false);
265 
266     QString tabButtonObjectName = getTabObjectName(tab);
267     QWidget *button = dashboard->findChild<QWidget *>(tabButtonObjectName);
268     return button != nullptr && button->isVisible();
269 }
270 #undef GT_METHOD_NAME
271 
272 #define GT_METHOD_NAME "getNodeText"
getNodeText(GUITestOpStatus & os,const QString & nodeId)273 QString GTUtilsDashboard::getNodeText(GUITestOpStatus &os, const QString &nodeId) {
274     return getExternalToolNode(os, nodeId)->content;
275 }
276 #undef GT_METHOD_NAME
277 
278 #define GT_METHOD_NAME "getChildrenNodesCount"
getChildrenNodesCount(GUITestOpStatus & os,const QString & nodeId)279 int GTUtilsDashboard::getChildrenNodesCount(GUITestOpStatus &os, const QString &nodeId) {
280     return nodeId == TREE_ROOT_ID ? getExternalToolsWidget(os)->getTopLevelNodes().size() : getExternalToolNode(os, nodeId)->children.count();
281 }
282 #undef GT_METHOD_NAME
283 
284 #define GT_METHOD_NAME "getChildNodes"
getChildNodes(GUITestOpStatus & os,const QString & nodeId)285 QList<ExternalToolsTreeNode *> GTUtilsDashboard::getChildNodes(GUITestOpStatus &os, const QString &nodeId) {
286     return nodeId == TREE_ROOT_ID ? getExternalToolsWidget(os)->getTopLevelNodes() : getExternalToolNode(os, nodeId)->children;
287 }
288 #undef GT_METHOD_NAME
289 
290 #define GT_METHOD_NAME "getChildNodeId"
getChildNodeId(GUITestOpStatus & os,const QString & nodeId,int childIndex)291 QString GTUtilsDashboard::getChildNodeId(GUITestOpStatus &os, const QString &nodeId, int childIndex) {
292     return getDescendantNodeId(os, nodeId, {childIndex});
293 }
294 #undef GT_METHOD_NAME
295 
296 #define GT_METHOD_NAME "getDescendantNodeId"
getDescendantNodeId(GUITestOpStatus & os,const QString & nodeId,const QList<int> & childIndexes)297 QString GTUtilsDashboard::getDescendantNodeId(GUITestOpStatus &os, const QString &nodeId, const QList<int> &childIndexes) {
298     QList<ExternalToolsTreeNode *> childNodes = getChildNodes(os, nodeId);
299     QString resultNodeId = nodeId;
300     for (int i : qAsConst(childIndexes)) {
301         GT_CHECK_RESULT(i >= 0 && i < childNodes.size(), "Illegal child index: " + QString::number(i) + ", nodes: " + childNodes.size(), "");
302         resultNodeId = childNodes[i]->objectName();
303         childNodes = childNodes[i]->children;
304     }
305     return resultNodeId;
306 }
307 #undef GT_METHOD_NAME
308 
309 #define GT_METHOD_NAME "getChildWithTextId"
getChildWithTextId(GUITestOpStatus & os,const QString & nodeId,const QString & text)310 QString GTUtilsDashboard::getChildWithTextId(GUITestOpStatus &os, const QString &nodeId, const QString &text) {
311     int childrenCount = getChildrenNodesCount(os, nodeId);
312     QString resultChildId;
313     QStringList quotedChildrenTexts;
314     for (int i = 0; i < childrenCount; i++) {
315         const QString currentChildId = getChildNodeId(os, nodeId, i);
316         const QString childText = getNodeText(os, currentChildId);
317         quotedChildrenTexts << "\'" + childText + "\'";
318         if (text == childText) {
319             GT_CHECK_RESULT(resultChildId.isEmpty(),
320                             QString("Expected text '%1' is not unique among the node with ID '%2' children")
321                                 .arg(text)
322                                 .arg(nodeId),
323                             "");
324             resultChildId = currentChildId;
325         }
326     }
327 
328     GT_CHECK_RESULT(!resultChildId.isEmpty(),
329                     QString("Child with text '%1' not found among the node with ID '%2' children; there are children with the following texts: %3")
330                         .arg(text)
331                         .arg(nodeId)
332                         .arg(quotedChildrenTexts.join(", ")),
333                     "");
334 
335     return resultChildId;
336 }
337 #undef GT_METHOD_NAME
338 
339 #define GT_METHOD_NAME "hasLimitationMessage"
hasLimitationMessage(GUITestOpStatus & os,const QString & nodeId)340 bool GTUtilsDashboard::hasLimitationMessage(GUITestOpStatus &os, const QString &nodeId) {
341     return !getLimitationMessage(os, nodeId).isEmpty();
342 }
343 #undef GT_METHOD_NAME
344 
345 #define GT_METHOD_NAME "getLimitationMessage"
getLimitationMessage(GUITestOpStatus & os,const QString & nodeId)346 QString GTUtilsDashboard::getLimitationMessage(GUITestOpStatus &os, const QString &nodeId) {
347     return nodeId == TREE_ROOT_ID ? getExternalToolsWidget(os)->getLimitationWarningHtml() : getExternalToolNode(os, nodeId)->limitationWarningHtml;
348 }
349 #undef GT_METHOD_NAME
350 
351 #define GT_METHOD_NAME "parseUrlFromContent"
parseUrlFromContent(GUITestOpStatus & os,const QString & content)352 static QString parseUrlFromContent(GUITestOpStatus &os, const QString &content) {
353     QString urlStartToken = "<a href=\"";
354     int urlStartTokenIdx = content.lastIndexOf(urlStartToken);
355     GT_CHECK_RESULT(urlStartTokenIdx > 0, "urlStartToken is not found, text: " + content, "");
356     int urlStartIdx = urlStartTokenIdx + urlStartToken.length();
357     int urlEndIdx = content.indexOf("\"", urlStartIdx + 1);
358     GT_CHECK_RESULT(urlEndIdx > 0, "urlEndToken is not found, text: " + content, "");
359     return content.mid(urlStartIdx, urlEndIdx - urlStartIdx);
360 }
361 #undef GT_METHOD_NAME
362 
363 #define GT_METHOD_NAME "getLimitationMessageLogUrl"
getLogUrlFromNodeLimitationMessage(GUITestOpStatus & os,const QString & nodeId)364 QString GTUtilsDashboard::getLogUrlFromNodeLimitationMessage(GUITestOpStatus &os, const QString &nodeId) {
365     QString limitationMessage = getLimitationMessage(os, nodeId);
366     return parseUrlFromContent(os, limitationMessage);
367 }
368 #undef GT_METHOD_NAME
369 
370 #define GT_METHOD_NAME "getLogUrlFromOutputContent"
getLogUrlFromOutputContent(GUITestOpStatus & os,const QString & outputNodeId)371 QString GTUtilsDashboard::getLogUrlFromOutputContent(GUITestOpStatus &os, const QString &outputNodeId) {
372     auto content = getExternalToolNode(os, outputNodeId)->content;
373     return parseUrlFromContent(os, content);
374 }
375 #undef GT_METHOD_NAME
376 
377 #define GT_METHOD_NAME "getCopyButtonSize"
getCopyButtonSize(GUITestOpStatus & os,const QString & toolRunNodeId)378 QSize GTUtilsDashboard::getCopyButtonSize(GUITestOpStatus &os, const QString &toolRunNodeId) {
379     return getCopyButton(os, toolRunNodeId)->rect().size();
380 }
381 #undef GT_METHOD_NAME
382 
383 #define GT_METHOD_NAME "clickCopyButton"
clickCopyButton(GUITestOpStatus & os,const QString & toolRunNodeId)384 void GTUtilsDashboard::clickCopyButton(GUITestOpStatus &os, const QString &toolRunNodeId) {
385     GTWidget::click(os, getCopyButton(os, toolRunNodeId));
386 }
387 #undef GT_METHOD_NAME
388 
389 #define GT_METHOD_NAME "isNodeVisible"
isNodeVisible(GUITestOpStatus & os,const QString & nodeId)390 bool GTUtilsDashboard::isNodeVisible(GUITestOpStatus &os, const QString &nodeId) {
391     return getExternalToolNode(os, nodeId)->isVisible();
392 }
393 #undef GT_METHOD_NAME
394 
395 #define GT_METHOD_NAME "isNodeCollapsed"
isNodeCollapsed(GUITestOpStatus & os,const QString & nodeId)396 bool GTUtilsDashboard::isNodeCollapsed(GUITestOpStatus &os, const QString &nodeId) {
397     return !getExternalToolNode(os, nodeId)->isExpanded();
398 }
399 #undef GT_METHOD_NAME
400 
401 #define GT_METHOD_NAME "collapseNode"
collapseNode(GUITestOpStatus & os,const QString & nodeId)402 void GTUtilsDashboard::collapseNode(GUITestOpStatus &os, const QString &nodeId) {
403     GT_CHECK(isNodeVisible(os, nodeId), QString("Node with ID '%1' is not visible. Some of the parent nodes are collapsed?").arg(nodeId));
404     GT_CHECK(!isNodeCollapsed(os, nodeId), QString("Node with ID '%1' is already collapsed.").arg(nodeId));
405     clickNodeTitle(os, getExternalToolNode(os, nodeId));
406     GT_CHECK(isNodeCollapsed(os, nodeId), QString("Node with ID '%1' was not collapsed.").arg(nodeId));
407 }
408 #undef GT_METHOD_NAME
409 
410 #define GT_METHOD_NAME "expandNode"
expandNode(GUITestOpStatus & os,const QString & nodeId)411 void GTUtilsDashboard::expandNode(GUITestOpStatus &os, const QString &nodeId) {
412     GT_CHECK(isNodeVisible(os, nodeId), QString("Node with ID '%1' is not visible. Some of the parent nodes are collapsed?").arg(nodeId));
413     GT_CHECK(isNodeCollapsed(os, nodeId), QString("Node with ID '%1' is already expanded.").arg(nodeId));
414     clickNodeTitle(os, getExternalToolNode(os, nodeId));
415     GT_CHECK(!isNodeCollapsed(os, nodeId), QString("Node with ID '%1' was not expanded.").arg(nodeId));
416 }
417 #undef GT_METHOD_NAME
418 
419 #define GT_METHOD_NAME "clickNodeTitle"
clickNodeTitle(GUITestOpStatus & os,ExternalToolsTreeNode * node)420 void GTUtilsDashboard::clickNodeTitle(GUITestOpStatus &os, ExternalToolsTreeNode *node) {
421     GT_CHECK(node != nullptr, "Node is null!");
422     GT_CHECK(node->badgeLabel->titleLabel != nullptr, "Node title label is null!");
423     GTWidget::click(os, node->badgeLabel->titleLabel);
424 }
425 #undef GT_METHOD_NAME
426 
427 #undef GT_CLASS_NAME
428 
429 }  // namespace U2
430