1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <config_folders.h>
12 #include <vcl/uitest/logger.hxx>
14 #include <rtl/bootstrap.hxx>
15 #include <rtl/ustrbuf.hxx>
16 #include <osl/file.hxx>
17 #include <vcl/ctrl.hxx>
18 #include <vcl/event.hxx>
19 #include <vcl/uitest/uiobject.hxx>
20 #include <vcl/uitest/eventdescription.hxx>
22 #include <com/sun/star/beans/PropertyValue.hpp>
27 bool isDialogWindow(vcl::Window
const* pWindow
)
29 WindowType nType
= pWindow
->GetType();
30 if (nType
== WindowType::DIALOG
|| nType
== WindowType::MODELESSDIALOG
)
33 // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
34 if (nType
>= WindowType::MESSBOX
&& nType
<= WindowType::QUERYBOX
)
37 if (nType
== WindowType::TABDIALOG
)
43 bool isTopWindow(vcl::Window
const* pWindow
)
45 WindowType eType
= pWindow
->GetType();
46 if (eType
== WindowType::FLOATINGWINDOW
)
48 return pWindow
->GetStyle() & WB_SYSTEMFLOATWIN
;
53 vcl::Window
* get_top_parent(vcl::Window
* pWindow
)
55 if (isDialogWindow(pWindow
) || isTopWindow(pWindow
))
58 vcl::Window
* pParent
= pWindow
->GetParent();
62 return get_top_parent(pParent
);
65 UITestLogger::UITestLogger()
68 static const char* pFile
= std::getenv("LO_COLLECT_UIINFO");
71 OUString
aDirPath("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
72 "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/uitest/");
73 rtl::Bootstrap::expandMacros(aDirPath
);
74 osl::Directory::createPath(aDirPath
);
75 OUString aFilePath
= aDirPath
+ OUString::fromUtf8(pFile
);
77 maStream
.Open(aFilePath
, StreamMode::READWRITE
| StreamMode::TRUNC
);
82 void UITestLogger::logCommand(std::u16string_view rAction
,
83 const css::uno::Sequence
<css::beans::PropertyValue
>& rArgs
)
88 OUStringBuffer
aBuffer(rAction
);
90 if (rArgs
.hasElements())
93 for (const css::beans::PropertyValue
& rProp
: rArgs
)
95 OUString aTypeName
= rProp
.Value
.getValueTypeName();
97 if (aTypeName
== "long" || aTypeName
== "short")
100 rProp
.Value
>>= nValue
;
101 aBuffer
.append("\"" + rProp
.Name
+ "\": " + OUString::number(nValue
) + ", ");
103 else if (aTypeName
== "unsigned long")
105 sal_uInt32 nValue
= 0;
106 rProp
.Value
>>= nValue
;
107 aBuffer
.append("\"" + rProp
.Name
+ "\": " + OUString::number(nValue
) + ", ");
109 else if (aTypeName
== "boolean")
112 rProp
.Value
>>= bValue
;
113 aBuffer
.append("\"" + rProp
.Name
+ "\": ");
115 aBuffer
.append("True, ");
117 aBuffer
.append("False, ");
123 OUString
aCommand(aBuffer
.makeStringAndClear());
124 maStream
.WriteLine(OUStringToOString(aCommand
, RTL_TEXTENCODING_UTF8
));
129 // most likely this should be recursive
130 bool child_windows_have_focus(VclPtr
<vcl::Window
> const& xUIElement
)
132 sal_Int32 nCount
= xUIElement
->GetChildCount();
133 for (sal_Int32 i
= 0; i
< nCount
; ++i
)
135 vcl::Window
* pChild
= xUIElement
->GetChild(i
);
136 if (pChild
->HasFocus())
140 if (child_windows_have_focus(VclPtr
<vcl::Window
>(pChild
)))
147 void UITestLogger::logAction(VclPtr
<Control
> const& xUIElement
, VclEventId nEvent
)
152 if (xUIElement
->get_id().isEmpty())
155 std::unique_ptr
<UIObject
> pUIObject
= xUIElement
->GetUITestFactory()(xUIElement
.get());
156 OUString aAction
= pUIObject
->get_action(nEvent
);
157 if (!xUIElement
->HasFocus() && !child_windows_have_focus(xUIElement
))
162 if (!aAction
.isEmpty())
163 maStream
.WriteLine(OUStringToOString(aAction
, RTL_TEXTENCODING_UTF8
));
166 void UITestLogger::logAction(vcl::Window
* const& xUIWin
, VclEventId nEvent
)
171 if (xUIWin
->get_id().isEmpty())
174 std::unique_ptr
<UIObject
> pUIObject
= xUIWin
->GetUITestFactory()(xUIWin
);
175 OUString aAction
= pUIObject
->get_action(nEvent
);
177 if (!aAction
.isEmpty())
178 maStream
.WriteLine(OUStringToOString(aAction
, RTL_TEXTENCODING_UTF8
));
181 void UITestLogger::log(std::u16string_view rString
)
189 maStream
.WriteLine(OUStringToOString(rString
, RTL_TEXTENCODING_UTF8
));
192 void UITestLogger::logKeyInput(VclPtr
<vcl::Window
> const& xUIElement
, const KeyEvent
& rEvent
)
197 //We need to check for Parent's ID in case the UI Element is SubEdit of Combobox/SpinField
199 = xUIElement
->get_id().isEmpty() ? xUIElement
->GetParent()->get_id() : xUIElement
->get_id();
203 sal_Unicode nChar
= rEvent
.GetCharCode();
204 sal_uInt16 nKeyCode
= rEvent
.GetKeyCode().GetCode();
205 bool bShift
= rEvent
.GetKeyCode().IsShift();
206 bool bMod1
= rEvent
.GetKeyCode().IsMod1();
207 bool bMod2
= rEvent
.GetKeyCode().IsMod2();
208 bool bMod3
= rEvent
.GetKeyCode().IsMod3();
210 std::map
<OUString
, sal_uInt16
> aKeyMap
211 = { { "ESC", KEY_ESCAPE
}, { "TAB", KEY_TAB
}, { "DOWN", KEY_DOWN
},
212 { "UP", KEY_UP
}, { "LEFT", KEY_LEFT
}, { "RIGHT", KEY_RIGHT
},
213 { "DELETE", KEY_DELETE
}, { "INSERT", KEY_INSERT
}, { "BACKSPACE", KEY_BACKSPACE
},
214 { "RETURN", KEY_RETURN
}, { "HOME", KEY_HOME
}, { "END", KEY_END
},
215 { "PAGEUP", KEY_PAGEUP
}, { "PAGEDOWN", KEY_PAGEDOWN
} };
218 for (const auto& itr
: aKeyMap
)
220 if (itr
.second
== nKeyCode
)
228 if (!aFound
.isEmpty() || bShift
|| bMod1
|| bMod2
|| bMod3
)
230 aKeyCode
= "{\"KEYCODE\": \"";
232 aKeyCode
+= "SHIFT+";
240 if (aFound
.isEmpty())
241 aKeyCode
+= OUStringChar(nChar
) + "\"}";
243 aKeyCode
+= aFound
+ "\"}";
247 aKeyCode
= "{\"TEXT\": \"" + OUStringChar(nChar
) + "\"}";
250 std::unique_ptr
<UIObject
> pUIObject
= xUIElement
->GetUITestFactory()(xUIElement
.get());
252 VclPtr
<vcl::Window
> pParent
= xUIElement
->GetParent();
254 while (pParent
&& !pParent
->IsTopWindow())
256 pParent
= pParent
->GetParent();
259 OUString aParentID
= pParent
? pParent
->get_id() : OUString();
263 if (pUIObject
->get_type() == "EditUIObject")
265 if (aParentID
.isEmpty())
267 VclPtr
<vcl::Window
> pParent_top
= get_top_parent(xUIElement
);
268 aParentID
= pParent_top
->get_id();
270 if (aParentID
.isEmpty())
272 aContent
+= "Type on '" + rID
+ "' " + aKeyCode
;
276 aContent
+= "Type on '" + rID
+ "' " + aKeyCode
+ " from " + aParentID
;
279 else if (pUIObject
->get_type() == "SwEditWinUIObject" && rID
== "writer_edit")
281 aContent
= "Type on writer " + aKeyCode
;
283 else if (pUIObject
->get_type() == "ScGridWinUIObject" && rID
== "grid_window")
285 aContent
= "Type on current cell " + aKeyCode
;
287 else if (pUIObject
->get_type() == "ImpressWindowUIObject" && rID
== "impress_win")
289 aContent
= "Type on impress " + aKeyCode
;
291 else if (pUIObject
->get_type() == "WindowUIObject" && rID
== "math_edit")
293 aContent
= "Type on math " + aKeyCode
;
295 else if (rID
== "draw_win")
297 aContent
= "Type on draw " + aKeyCode
;
301 if (aParentID
.isEmpty())
303 VclPtr
<vcl::Window
> pParent_top
= get_top_parent(xUIElement
);
304 aParentID
= pParent_top
->get_id();
306 if (aParentID
.isEmpty())
308 aContent
= "Type on '" + rID
+ "' " + aKeyCode
;
312 aContent
= "Type on '" + rID
+ "' " + aKeyCode
+ " from " + aParentID
;
315 maStream
.WriteLine(OUStringToOString(aContent
, RTL_TEXTENCODING_UTF8
));
320 OUString
StringMapToOUString(const std::map
<OUString
, OUString
>& rParameters
)
322 if (rParameters
.empty())
325 OUStringBuffer
aParameterString(static_cast<int>(rParameters
.size() * 32));
326 aParameterString
.append(" {");
328 for (std::map
<OUString
, OUString
>::const_iterator itr
= rParameters
.begin();
329 itr
!= rParameters
.end(); ++itr
)
331 if (itr
!= rParameters
.begin())
332 aParameterString
.append(", ");
333 aParameterString
.append("\"" + itr
->first
+ "\": \"" + itr
->second
+ "\"");
336 aParameterString
.append("}");
338 return aParameterString
.makeStringAndClear();
341 const OUString
& GetValueInMapWithIndex(const std::map
<OUString
, OUString
>& rParameters
,
346 std::map
<OUString
, OUString
>::const_iterator itr
= rParameters
.begin();
348 for (; itr
!= rParameters
.end() && j
< index
; ++itr
, ++j
)
351 assert(itr
!= rParameters
.end());
356 const OUString
& GetKeyInMapWithIndex(const std::map
<OUString
, OUString
>& rParameters
,
361 std::map
<OUString
, OUString
>::const_iterator itr
= rParameters
.begin();
363 for (; itr
!= rParameters
.end() && j
< index
; ++itr
, ++j
)
366 assert(itr
!= rParameters
.end());
372 void UITestLogger::logEvent(const EventDescription
& rDescription
)
374 OUString aParameterString
= StringMapToOUString(rDescription
.aParameters
);
376 //here we will customize our statements depending on the caller of this function
378 //first check on general commands
379 if (rDescription
.aAction
== "SET")
381 aLogLine
= "Set Zoom to " + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
383 else if (rDescription
.aAction
== "SIDEBAR")
385 aLogLine
= "From SIDEBAR Choose " + aParameterString
;
387 else if (rDescription
.aKeyWord
== "ValueSet")
389 aLogLine
= "Choose element with position "
390 + GetValueInMapWithIndex(rDescription
.aParameters
, 0) + " in '"
391 + rDescription
.aID
+ "' from '" + rDescription
.aParent
+ "'";
393 else if (rDescription
.aAction
== "SELECT" && rDescription
.aID
.isEmpty())
395 aLogLine
= "Select " + aParameterString
;
397 else if (rDescription
.aID
== "writer_edit")
399 if (rDescription
.aAction
== "GOTO")
401 aLogLine
= "GOTO page number " + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
403 else if (rDescription
.aAction
== "SELECT")
405 OUString to
= GetValueInMapWithIndex(rDescription
.aParameters
, 0);
406 OUString from
= GetValueInMapWithIndex(rDescription
.aParameters
, 1);
407 aLogLine
= "Select from Pos " + from
+ " to Pos " + to
;
409 else if (rDescription
.aAction
== "CREATE_TABLE")
411 OUString size
= GetValueInMapWithIndex(rDescription
.aParameters
, 0);
412 aLogLine
= "Create Table with " + size
;
415 else if (rDescription
.aAction
== "COPY")
417 aLogLine
= "Copy the Selected Text";
419 else if (rDescription
.aAction
== "CUT")
421 aLogLine
= "Cut the Selected Text";
423 else if (rDescription
.aAction
== "PASTE")
425 aLogLine
= "Paste in the Current Cursor Location";
427 else if (rDescription
.aAction
== "BREAK_PAGE")
429 aLogLine
= "Insert Break Page";
432 else if (rDescription
.aID
== "grid_window")
434 if (rDescription
.aAction
== "SELECT")
436 OUString type
= GetKeyInMapWithIndex(rDescription
.aParameters
, 0);
437 if (type
== "CELL" || type
== "RANGE")
439 aLogLine
= "Select from calc" + aParameterString
;
441 else if (type
== "TABLE")
443 aLogLine
= "Switch to sheet number "
444 + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
447 else if (rDescription
.aAction
== "LAUNCH")
449 aLogLine
= "Launch" + GetKeyInMapWithIndex(rDescription
.aParameters
, 2) + " from Col "
450 + GetValueInMapWithIndex(rDescription
.aParameters
, 2) + " and Row "
451 + GetValueInMapWithIndex(rDescription
.aParameters
, 1);
453 else if (rDescription
.aAction
== "DELETE_CONTENT")
455 aLogLine
= "Remove Content from This " + aParameterString
;
457 else if (rDescription
.aAction
== "DELETE_CELLS")
459 aLogLine
= "Delete The Cells in" + aParameterString
;
461 else if (rDescription
.aAction
== "INSERT_CELLS")
463 aLogLine
= "Insert Cell around the " + aParameterString
;
465 else if (rDescription
.aAction
== "CUT")
467 aLogLine
= "CUT the selected " + aParameterString
;
469 else if (rDescription
.aAction
== "COPY")
471 aLogLine
= "COPY the selected " + aParameterString
;
473 else if (rDescription
.aAction
== "PASTE")
475 aLogLine
= "Paste in the " + aParameterString
;
477 else if (rDescription
.aAction
== "MERGE_CELLS")
479 aLogLine
= "Merge " + aParameterString
;
481 else if (rDescription
.aAction
== "UNMERGE_CELL")
483 aLogLine
= "Delete the merged " + aParameterString
;
485 else if (rDescription
.aAction
== "Rename_Sheet")
487 aLogLine
= "Rename The Selected Tab to \""
488 + GetValueInMapWithIndex(rDescription
.aParameters
, 0) + "\"";
490 else if (rDescription
.aAction
== "InsertTab")
492 aLogLine
= "Insert New Tab ";
494 else if (rDescription
.aAction
== "COMMENT")
496 OUString type
= GetKeyInMapWithIndex(rDescription
.aParameters
, 0);
499 aLogLine
= "Open Comment";
501 else if (type
== "CLOSE")
503 aLogLine
= "Close Comment";
507 else if (rDescription
.aID
== "impress_win_or_draw_win")
509 if (rDescription
.aAction
== "Insert_New_Page_or_Slide")
511 if (UITestLogger::getInstance().getAppName() == "impress")
513 aLogLine
= "Insert New Slide at Position "
514 + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
516 else if (UITestLogger::getInstance().getAppName() == "draw")
518 aLogLine
= "Insert New Page at Position "
519 + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
522 else if (rDescription
.aAction
== "Delete_Slide_or_Page")
524 if (UITestLogger::getInstance().getAppName() == "impress")
527 = "Delete Slide number " + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
529 else if (UITestLogger::getInstance().getAppName() == "draw")
532 = "Delete Page number " + GetValueInMapWithIndex(rDescription
.aParameters
, 0);
535 else if (rDescription
.aAction
== "Duplicate")
537 aLogLine
= "Duplicate The Selected Slide ";
539 else if (rDescription
.aAction
== "RENAME")
541 if (UITestLogger::getInstance().getAppName() == "impress")
543 aLogLine
= "Rename The Selected Slide from \""
544 + GetValueInMapWithIndex(rDescription
.aParameters
, 1) + "\" to \""
545 + GetValueInMapWithIndex(rDescription
.aParameters
, 0) + "\"";
547 else if (UITestLogger::getInstance().getAppName() == "draw")
549 aLogLine
= "Rename The Selected Page from \""
550 + GetValueInMapWithIndex(rDescription
.aParameters
, 1) + "\" to \""
551 + GetValueInMapWithIndex(rDescription
.aParameters
, 0) + "\"";
555 else if (rDescription
.aKeyWord
== "SwEditWinUIObject")
557 if (rDescription
.aAction
== "LEAVE")
559 aLogLine
= "Leave '" + rDescription
.aID
+ "'";
561 else if (rDescription
.aAction
== "SHOW")
563 aLogLine
= "Show '" + rDescription
.aID
+ "'";
565 else if (rDescription
.aAction
== "HIDE")
567 aLogLine
= "Hide '" + rDescription
.aID
+ "'";
569 else if (rDescription
.aAction
== "DELETE")
571 aLogLine
= "Delete '" + rDescription
.aID
+ "'";
573 else if (rDescription
.aAction
== "SETRESOLVED")
575 aLogLine
= "Resolve '" + rDescription
.aID
+ "'";
578 else if (rDescription
.aParent
== "element_selector")
580 aLogLine
= "Select element no " + rDescription
.aID
+ " From " + rDescription
.aParent
;
582 else if (rDescription
.aKeyWord
== "MenuButton")
584 if (rDescription
.aAction
== "OPENLIST")
586 aLogLine
= "Open List From " + rDescription
.aID
;
588 else if (rDescription
.aAction
== "CLOSELIST")
590 aLogLine
= "Close List From " + rDescription
.aID
;
592 else if (rDescription
.aAction
== "OPENFROMLIST")
594 aLogLine
= "Select item no " + GetValueInMapWithIndex(rDescription
.aParameters
, 0)
595 + " From List of " + rDescription
.aID
;
598 else if (rDescription
.aKeyWord
== "VerticalTab")
600 aLogLine
= "Choose Tab number " + GetValueInMapWithIndex(rDescription
.aParameters
, 0)
601 + " in '" + rDescription
.aID
+ "'";
605 aLogLine
= rDescription
.aKeyWord
+ " Action:" + rDescription
.aAction
+ " Id:"
606 + rDescription
.aID
+ " Parent:" + rDescription
.aParent
+ aParameterString
;
611 UITestLogger
& UITestLogger::getInstance()
613 ImplSVData
* const pSVData
= ImplGetSVData();
616 if (!pSVData
->maFrameData
.m_pUITestLogger
)
618 pSVData
->maFrameData
.m_pUITestLogger
.reset(new UITestLogger
);
621 return *pSVData
->maFrameData
.m_pUITestLogger
;
624 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */