robotframework-dialogsplus 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ ### Python ###
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+
7
+ # Distribution / packaging
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ share/python-wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+ MANIFEST
26
+
27
+ # PyInstaller
28
+ # Usually these files are written by a python script from a template
29
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ utests/.pytest_cache/
39
+ utests/.coverage
40
+ utests/coverage.xml
41
+ utests/htmlcov/
42
+
43
+ # Translations
44
+ *.mo
45
+ *.pot
46
+
47
+ # IPython
48
+ profile_default/
49
+ ipython_config.py
50
+
51
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
52
+ __pypackages__/
53
+
54
+ # Environments
55
+ .venv
56
+
57
+ # mypy
58
+ .mypy_cache/
59
+ .dmypy.json
60
+ dmypy.json
61
+
62
+ # Pyre type checker
63
+ .pyre/
64
+
65
+ # pytype static type analyzer
66
+ .pytype/
67
+
68
+ # Cython debug symbols
69
+ cython_debug/
70
+
71
+ # ruff
72
+ .ruff_cache/
73
+
74
+
75
+
76
+ # Robot Framework output files in atests/
77
+ results
78
+ .vscode
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotframework-dialogsplus
3
+ Version: 0.1.0
4
+ Summary: A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
5
+ Author-email: Alpha-Centauri-00 <Alpha@Centauri.c0m>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: customtkinter>=5.2.2
9
+ Requires-Dist: pyyaml>=6.0.3
10
+ Requires-Dist: robotframework>=7.3.2
11
+ Description-Content-Type: text/markdown
12
+
13
+ # DialogsPlus
14
+ A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
15
+
16
+ ### ⚠️ Known Limitations
17
+
18
+ - Not supported in headless environments such as CI/CD pipelines (e.g., Jenkins, GitHub Actions)
19
+
20
+
21
+ **DialogsPlus** is a user-friendly and fully customizable dialog library for [Robot Framework](https://robotframework.org/), built on top of [`customtkinter`](https://github.com/TomSchimansky/CustomTkinter). It extends the built-in dialog functionality with rich GUI dialogs that are **stylish**, **modern**, and **configurable** via a simple `config.yaml` file.
22
+
23
+ ---
24
+
25
+ ## ✨ Features
26
+
27
+ - Easy-to-use dialogs for user interaction during test runs
28
+ - Full GUI-based interface using `customtkinter`
29
+ - Customizable look and feel (colors, fonts, sizes, etc.) via `config.yaml`
30
+ - Drop-in replacement for standard Robot Framework dialogs
31
+ - Supports dynamic sizing based on input fields or options
32
+
33
+ ---
34
+
35
+
36
+
37
+ Pull requests are welcome! More info coming soon.
@@ -0,0 +1,25 @@
1
+ # DialogsPlus
2
+ A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
3
+
4
+ ### ⚠️ Known Limitations
5
+
6
+ - Not supported in headless environments such as CI/CD pipelines (e.g., Jenkins, GitHub Actions)
7
+
8
+
9
+ **DialogsPlus** is a user-friendly and fully customizable dialog library for [Robot Framework](https://robotframework.org/), built on top of [`customtkinter`](https://github.com/TomSchimansky/CustomTkinter). It extends the built-in dialog functionality with rich GUI dialogs that are **stylish**, **modern**, and **configurable** via a simple `config.yaml` file.
10
+
11
+ ---
12
+
13
+ ## ✨ Features
14
+
15
+ - Easy-to-use dialogs for user interaction during test runs
16
+ - Full GUI-based interface using `customtkinter`
17
+ - Customizable look and feel (colors, fonts, sizes, etc.) via `config.yaml`
18
+ - Drop-in replacement for standard Robot Framework dialogs
19
+ - Supports dynamic sizing based on input fields or options
20
+
21
+ ---
22
+
23
+
24
+
25
+ Pull requests are welcome! More info coming soon.
@@ -0,0 +1,14 @@
1
+ title: "Custom Dialog"
2
+ width: 400
3
+ height: 200
4
+ theme: "green"
5
+ appearance_mode: "light"
6
+ button_fg_color: "#aaa"
7
+
8
+ #
9
+ button_width: 150
10
+ button_height: 40
11
+ label_font: ["Calibri", 14]
12
+ entry_height: 30
13
+ entry_width: 300
14
+ spacing: 12
@@ -0,0 +1,87 @@
1
+ *** Settings ***
2
+ Library DialogsPlus #config=D:/robotframework-dialogsplus/atests/config.yaml
3
+ Library OperatingSystem
4
+
5
+ Suite Setup Set Log Level level=TRACE
6
+ Test Setup Log Test Case Name
7
+
8
+
9
+ *** Keywords ***
10
+ Log Test Case Name
11
+ [Tags] robot:flatten
12
+ Log <p style="background-color: #06bdb1; font-weight: bold; display: inline-block; padding: 4px;">*** Running Test: ${TEST NAME} ***</p> html=${True}
13
+
14
+
15
+ *** Variables ***
16
+
17
+ @{fields_val} username password email phone
18
+ @{fields} username password
19
+ &{default_val} username=admin password=P@55 phone=1234567
20
+ &{default} username=user1 password=P@55
21
+
22
+ *** Test Cases ***
23
+
24
+ Get Value From User Default
25
+ ${result} Get Value From User prompt=Enter your name: default=Robot framework
26
+ Should Be Equal ${result} Robot framework
27
+
28
+ Run Manual Steps Executes
29
+ ${steps} Create List Open the app Click Start button Verify status
30
+ Run Manual Steps ${steps}
31
+ Log Manual steps executed successfully
32
+
33
+ Count Down Runs
34
+ Count Down 3
35
+ Log Countdown executed for 3 seconds
36
+
37
+ Get Confirmation Returns Boolean
38
+ ${result} Get Confirmation Are you sure?
39
+ Should Be True isinstance(${result}, bool)
40
+
41
+ Get Multi Value
42
+ ${result} Get Multi Value ${fields} default=${default}
43
+ Should Be Equal ${result}[username] user1
44
+
45
+
46
+ Get Multi Value Multiple Fields
47
+ ${result} Get Multi Value ${fields_val} default=${default_val}
48
+ Should Be Equal ${result}[password] P@55
49
+ Should Be Equal ${result}[phone] 1234567
50
+
51
+ Choose Single XML File
52
+ ${XML_FILETYPES} Evaluate [("xml files", "*.xml")]
53
+ ${result}= Choose File message=Select Single XML File filetypes=${XML_FILETYPES}
54
+ Should Contain ${result} .xml
55
+
56
+ Choose Multiple HTML Files
57
+ ${HTML_FILETYPES} Evaluate [("HTML", "*.html")]
58
+ ${result}= Choose File message=Select Multiple HTML Files filetypes=${HTML_FILETYPES} multiple=True
59
+ Should Contain ${result}[0] .html
60
+
61
+ Choose Folder Test
62
+ ${result}= Choose Folder message=Select Any Directory
63
+ Directory Should Exist ${result}
64
+
65
+ Single Ceckbox Test
66
+ ${r} Confirm With Checkbox message=Do you accept the terms? checkbox_text=I accept, no matter what!
67
+ Should Be True ${r}
68
+
69
+ Select Many Checkbox Test
70
+ ${r} Select Options With Checkboxes message=Select as much as you want options=${fields_val}
71
+ Should Not Be True ${r}[username]
72
+ Should Not Be True ${r}[password]
73
+ Should Not Be True ${r}[email]
74
+ Should Not Be True ${r}[phone]
75
+
76
+ Select Many Checkbox With Defaults Test
77
+ @{Contacts} Create List Email SMS Phone Slack Discord
78
+ ${selected}= Select Options With Checkboxes
79
+ ... message=Choose your preferences
80
+ ... options=${Contacts}
81
+ ... defaults=Email,SMS # Default Selected!
82
+
83
+ Should Be True ${selected}[Email]
84
+ Should Be True ${selected}[SMS]
85
+ Should Not Be True ${selected}[Phone]
86
+ Should Not Be True ${selected}[Slack]
87
+ Should Not Be True ${selected}[Discord]
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "robotframework-dialogsplus"
3
+ version = "0.1.0"
4
+ description = "A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Alpha-Centauri-00", email = "Alpha@Centauri.c0m" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+
12
+ dependencies = [
13
+ "customtkinter>=5.2.2",
14
+ "pyyaml>=6.0.3",
15
+ "robotframework>=7.3.2",
16
+ ]
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/DialogsPlus"]
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "robotframework-tidy>=4.18.0",
28
+ ]
29
+ [tool.ruff]
30
+ ignore = ["E722"]
31
+
@@ -0,0 +1,5 @@
1
+ # src/DialogsPlus/__init__.py
2
+
3
+ from .dialogsplus import DialogsPlus
4
+
5
+ __all__ = ["DialogsPlus"]
@@ -0,0 +1,90 @@
1
+ from robot.api.deco import keyword
2
+ import os
3
+ from DialogsPlus.utils.config import DialogConfig
4
+ from DialogsPlus.widgets.wrappers import ( GetValueFromUserDialog,
5
+ ExecuteManualStepDialog,
6
+ CountdownDialogRunner,
7
+ GetConfirmationFromUser,
8
+ MultiValueInput,
9
+ ChooseFromFileDialog,
10
+ ChooseFolderDialog,
11
+ ConfirmWithCheckbox,
12
+ SelectOptionsWithCheckboxes)
13
+
14
+
15
+ ROBOT_LIBRARY_SCOPE = 'SUITE'
16
+
17
+ class DialogsPlus:
18
+
19
+ def __init__(self, config=None):
20
+
21
+ if config and os.path.exists(config):
22
+ self.config = DialogConfig.from_yaml(config)
23
+ else:
24
+ self.config = DialogConfig() # use defaults
25
+
26
+
27
+ @keyword
28
+ def get_value_from_user(self, prompt="Enter value:", default=""):
29
+ return GetValueFromUserDialog.show(prompt,default,config=self.config)
30
+
31
+ @keyword
32
+ def run_manual_steps(self, steps):
33
+ ExecuteManualStepDialog.run_steps(steps, config=self.config)
34
+
35
+ @keyword
36
+ def count_down(self, seconds):
37
+ CountdownDialogRunner.show(int(seconds), config=self.config)
38
+
39
+ @keyword
40
+ def get_confirmation(self, message):
41
+ return GetConfirmationFromUser.show(message=message,config=self.config)
42
+
43
+ @keyword
44
+ def get_multi_value(self, fields, default=None):
45
+ fields_list = fields if isinstance(fields, list) else [fields]
46
+ calculated_height = 150 + (len(fields_list) * 40) + 60
47
+ #max_field_length = max(len(field) for field in fields_list)
48
+ max_field_length = 20
49
+ calculated_width = 300 + (max_field_length * 8)
50
+ self.config.height = calculated_height
51
+ self.config.width = calculated_width
52
+ return MultiValueInput.run_multival(fields=fields,defaults=default,config=self.config)
53
+
54
+ @keyword
55
+ def choose_file(self, message="", filetypes=None, multiple=False):
56
+ return ChooseFromFileDialog.show( message, filetypes, multiple, self.config)
57
+
58
+ @keyword
59
+ def choose_folder(self, message):
60
+ return ChooseFolderDialog.show(message, self.config)
61
+
62
+
63
+ @keyword
64
+ def confirm_with_checkbox(self, message, checkbox_text="I agree"):
65
+ return ConfirmWithCheckbox.show(message, checkbox_text, self.config)
66
+
67
+
68
+ @keyword
69
+ def select_options_with_checkboxes(self, message, options, defaults=None):
70
+ """Show multiple checkboxes and return selected options as dictionary."""
71
+
72
+ options_list = options if isinstance(options, list) else options.split('|')
73
+
74
+ # Base sizing
75
+ num_options = len(options_list)
76
+ max_option_length = max(len(opt) for opt in options_list)
77
+ message_length = len(message)
78
+
79
+ # Height: base + checkboxes + some buffer
80
+ calculated_height = 180 + (num_options * 40)
81
+
82
+ # Width: consider both message and longest option
83
+ width_from_message = min(600, max(300, message_length * 7))
84
+ width_from_options = max(300, 200 + (max_option_length * 8))
85
+ calculated_width = max(width_from_message, width_from_options)
86
+
87
+ self.config.height = calculated_height
88
+ self.config.width = calculated_width
89
+
90
+ return SelectOptionsWithCheckboxes.show(message, options, defaults, self.config)
@@ -0,0 +1,83 @@
1
+ # dialog_config.py
2
+
3
+ import yaml
4
+
5
+ class DialogConfig:
6
+ def __init__(
7
+ self,
8
+ title="Robot Framework",
9
+ width=400,
10
+ height=150,
11
+ theme="blue",
12
+ appearance_mode="system",
13
+
14
+ button_width=120,
15
+ button_height=32,
16
+ label_font=("Courier New", 16, "bold"),
17
+ entry_width= 200,
18
+ entry_height=28,
19
+ spacing=10,
20
+ button_fg_color="#06bdb1",
21
+ button_font=("Courier New", 16, "bold"),
22
+ label_text_color="white",
23
+ button_text_color="black",
24
+ button_hover_color="#57b7b0",
25
+ entry_font=("Courier New", 14),
26
+ entry_text_color="white",
27
+ entry_fg_color="#212121",
28
+ entry_border_color="#46fff4",
29
+ frame_fg_color = "transparent",
30
+ progress_bar_width = 300,
31
+ progress_bar_height = 12,
32
+ progress_bar_color = "#00c0b5",
33
+
34
+
35
+ ):
36
+ self.title = title
37
+ self.width = width
38
+ self.height = height
39
+ self.theme = theme
40
+ self.appearance_mode = appearance_mode
41
+
42
+ self.button_width = button_width
43
+ self.button_height = button_height
44
+ self.label_font = label_font
45
+ self.entry_width = entry_width
46
+ self.entry_height = entry_height
47
+ self.spacing = spacing
48
+ self.button_fg_color=button_fg_color
49
+ self.button_font = button_font
50
+ self.label_text_color = label_text_color
51
+ self.button_text_color = button_text_color
52
+ self.button_hover_color = button_hover_color
53
+ self.entry_font = entry_font
54
+ self.entry_text_color = entry_text_color
55
+ self.entry_fg_color = entry_fg_color
56
+ self.entry_border_color = entry_border_color
57
+ self.frame_fg_color = frame_fg_color
58
+ self.progress_bar_width = progress_bar_width
59
+ self.progress_bar_height = progress_bar_height
60
+ self.progress_bar_color = progress_bar_color
61
+
62
+
63
+ @classmethod
64
+ def from_yaml(cls, path: str):
65
+ print(f"[DEBUG] Received config path: {path}")
66
+ with open(path, "r", encoding="utf-8") as f:
67
+ data = yaml.safe_load(f)
68
+
69
+ return cls(
70
+ title=data.get("title", "Dialog"),
71
+ width=data.get("width", 300),
72
+ height=data.get("height", 150),
73
+ theme=data.get("theme", "blue"),
74
+ appearance_mode=data.get("appearance_mode", "system"),
75
+
76
+ button_width=data.get("button_width", 120),
77
+ button_height=data.get("button_height", 32),
78
+ label_font=tuple(data.get("label_font", ("Arial", 12))),
79
+ entry_height=data.get("entry_height", 28),
80
+ entry_width=data.get("entry_width",60),
81
+ spacing=data.get("spacing", 10),
82
+ button_fg_color=data.get("button_fg_color", "#06bdb1")
83
+ )
@@ -0,0 +1,169 @@
1
+ import customtkinter as ctk
2
+ from customtkinter import BooleanVar, IntVar
3
+ from DialogsPlus.utils.config import DialogConfig
4
+ from tkinter import filedialog
5
+ import os
6
+
7
+
8
+ # Export for use in other modules
9
+ __all__ = [
10
+ 'BaseDialogRunner',
11
+ 'BaseDialog',
12
+ 'ctk',
13
+ 'BooleanVar',
14
+ 'filedialog',
15
+ 'IntVar'
16
+ ]
17
+
18
+ class BaseDialogRunner:
19
+ _theme_initialized = False
20
+
21
+ @staticmethod
22
+ def _center_window(app, config):
23
+ app.update_idletasks()
24
+
25
+ screen_width = app.winfo_screenwidth()
26
+ screen_height = app.winfo_screenheight()
27
+
28
+ x = (screen_width - config.width) // 2
29
+ y = (screen_height - config.height) // 2
30
+ app.geometry(f"{config.width}x{config.height}+{x}+{y}")
31
+
32
+ @staticmethod
33
+ def create_app(config: DialogConfig):
34
+ # Initialize theme once on first dialog creation
35
+ if not BaseDialogRunner._theme_initialized:
36
+ ctk.set_appearance_mode(config.appearance_mode)
37
+ ctk.set_default_color_theme(config.theme)
38
+ BaseDialogRunner._theme_initialized = True
39
+
40
+ app = ctk.CTk()
41
+ app.title(config.title)
42
+
43
+ icon_path = os.path.join(os.path.dirname(__file__), "assets", "robot.ico")
44
+ try:
45
+ app.iconbitmap(icon_path)
46
+ except (FileNotFoundError, Exception):
47
+ pass
48
+
49
+ BaseDialogRunner._center_window(app, config)
50
+ return app
51
+
52
+ @staticmethod
53
+ def run_dialog(ui_builder_func, config: DialogConfig):
54
+ app = BaseDialogRunner.create_app(config)
55
+ ui_builder_func(app)
56
+ app.mainloop()
57
+
58
+ app.withdraw()
59
+
60
+ try:
61
+ after_ids = app.tk.call('after', 'info')
62
+ for after_id in after_ids:
63
+ try:
64
+ app.after_cancel(after_id)
65
+ except:
66
+ pass
67
+ except:
68
+ pass
69
+
70
+ app.update_idletasks()
71
+
72
+ try:
73
+ app.quit()
74
+ except:
75
+ pass
76
+
77
+ try:
78
+ app.destroy()
79
+ except:
80
+ pass
81
+
82
+
83
+ class BaseDialog:
84
+
85
+ def __init__(self, config=None):
86
+ self.config = config if config else DialogConfig()
87
+ self.result = {}
88
+
89
+ def create_button(self, parent, text, command, **kwargs):
90
+ """Create a button with config styling"""
91
+ return ctk.CTkButton(
92
+ parent,
93
+ text=text,
94
+ command=command,
95
+ font=self.config.button_font,
96
+ fg_color=self.config.button_fg_color,
97
+ text_color=self.config.button_text_color,
98
+ hover_color=self.config.button_hover_color,
99
+ width=kwargs.get('width', self.config.button_width),
100
+ height=kwargs.get('height', self.config.button_height),
101
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height']}
102
+ )
103
+
104
+ def create_label(self, parent, text, **kwargs):
105
+ """Create a label with config styling"""
106
+ return ctk.CTkLabel(
107
+ parent,
108
+ text=text,
109
+ font=self.config.label_font,
110
+ text_color=self.config.label_text_color,
111
+ anchor=kwargs.get('anchor', "center"),
112
+ justify=kwargs.get('justify', "left"),
113
+ wraplength=kwargs.get('wraplength', 0),
114
+ **{k: v for k, v in kwargs.items() if k not in ['anchor', 'justify', 'wraplength']}
115
+ )
116
+
117
+ def create_entry(self, parent, **kwargs):
118
+ """Create an entry with consistent styling from config"""
119
+ return ctk.CTkEntry(
120
+ parent,
121
+ width=kwargs.get('width', self.config.entry_width),
122
+ height=kwargs.get('height', self.config.entry_height),
123
+ font=kwargs.get('font', self.config.entry_font),
124
+ text_color=kwargs.get('text_color', self.config.entry_text_color),
125
+ fg_color=kwargs.get('fg_color', self.config.entry_fg_color),
126
+ border_color=kwargs.get('border_color', self.config.entry_border_color),
127
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height', 'font', 'text_color', 'fg_color', 'border_color']}
128
+ )
129
+
130
+ def create_frame(self, parent, **kwargs):
131
+ return ctk.CTkFrame(
132
+ parent,
133
+ fg_color=kwargs.get('fg_color', self.config.frame_fg_color),
134
+ **{k: v for k, v in kwargs.items() if k not in ['fg_color']} # ← Add this
135
+ )
136
+
137
+ def create_progress_bar(self, parent, **kwargs):
138
+ return ctk.CTkProgressBar(
139
+ parent,
140
+ width=kwargs.get('width', self.config.progress_bar_width),
141
+ height=kwargs.get('height', self.config.progress_bar_height),
142
+ progress_color=kwargs.get('progress_color', self.config.progress_bar_color),
143
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height', 'progress_color']}
144
+ )
145
+
146
+ def create_checkbox(self, parent, text="", **kwargs):
147
+ """Create a checkbox with consistent styling from config"""
148
+ return ctk.CTkCheckBox(
149
+ parent,
150
+ text=text,
151
+ font=kwargs.get('font', self.config.label_font),
152
+ text_color=kwargs.get('text_color', self.config.label_text_color),
153
+ fg_color=kwargs.get('fg_color', self.config.button_fg_color),
154
+ hover_color=kwargs.get('hover_color', self.config.button_hover_color),
155
+ **{k: v for k, v in kwargs.items() if k not in ['font', 'text_color', 'fg_color', 'hover_color']}
156
+ )
157
+
158
+
159
+ # testing this shit
160
+
161
+ def show(self):
162
+ def ui(app):
163
+ self.build_ui(app)
164
+
165
+ BaseDialogRunner.run_dialog(ui, self.config)
166
+ return self.result
167
+
168
+ def build_ui(self, app):
169
+ raise NotImplementedError