1
- from typing import Any , Dict , List , Optional , Protocol , cast , runtime_checkable
1
+ # type: ignore[reportAttributeAccessIssue]
2
+ # Ignore attribute access issues when type checking because mixin
3
+ # class depends on other class lineage to supply things. We could use
4
+ # a protocol definition here instead…
2
5
3
6
from bs4 import BeautifulSoup
4
7
5
- # directive needed because protocol classes are so brief
6
- # pylint: disable=too-few-public-methods
7
-
8
-
9
- @runtime_checkable
10
- class WebElement (Protocol ):
11
- """Protocol for WebElement-like objects with get_attribute."""
12
-
13
- def get_attribute (self , name : str ) -> str :
14
- ...
15
-
16
-
17
- @runtime_checkable
18
- class WebDriver (Protocol ):
19
- """Protocol for WebDriver-like objects with execute_script."""
20
-
21
- def execute_script (self , script : str , * args : Any ) -> Any :
22
- ...
23
-
24
8
25
9
class DashPageMixin :
26
- """Mixin class for Dash page with DOM access methods.
27
-
28
- This mixin is intended to be used with a class that provides:
29
- 1. A 'driver' attribute with execute_script method
30
- 2. A 'find_element' method that returns elements with get_attribute method
31
-
32
- The mixin provides properties like dash_entry_locator and methods to
33
- interact with the Dash application's DOM and state.
34
- """
35
-
36
- driver : WebDriver # Expected to be provided by the parent class
37
-
38
- def find_element (self , locator : str ) -> WebElement :
39
- """Find an element by locator.
40
-
41
- This is expected to be implemented by the parent class.
42
- """
43
- raise NotImplementedError (
44
- "find_element must be implemented by the parent class"
10
+ def _get_dash_dom_by_attribute (self , attr ):
11
+ return BeautifulSoup (
12
+ self .find_element (self .dash_entry_locator ).get_attribute (attr ), "lxml"
45
13
)
46
14
47
15
@property
48
- def dash_entry_locator (self ) -> str :
49
- """CSS selector for Dash app entry point."""
50
- return "#react-entry-point"
51
-
52
- @property
53
- def devtools_error_count_locator (self ) -> str :
54
- """CSS selector for devtools error count."""
16
+ def devtools_error_count_locator (self ):
55
17
return ".test-devtools-error-count"
56
18
57
- def _get_dash_dom_by_attribute (self , attr : str ) -> BeautifulSoup :
58
- """Get BeautifulSoup representation of element's attribute."""
59
- element = self .find_element (self .dash_entry_locator )
60
- return BeautifulSoup (element .get_attribute (attr ), "lxml" )
19
+ @property
20
+ def dash_entry_locator (self ):
21
+ return "#react-entry-point"
61
22
62
23
@property
63
- def dash_outerhtml_dom (self ) -> BeautifulSoup :
64
- """Get BeautifulSoup representation of outerHTML."""
24
+ def dash_outerhtml_dom (self ):
65
25
return self ._get_dash_dom_by_attribute ("outerHTML" )
66
26
67
27
@property
68
- def dash_innerhtml_dom (self ) -> BeautifulSoup :
69
- """Get BeautifulSoup representation of innerHTML."""
28
+ def dash_innerhtml_dom (self ):
70
29
return self ._get_dash_dom_by_attribute ("innerHTML" )
71
30
72
31
@property
73
- def redux_state_paths (self ) -> Dict [str , Any ]:
74
- """Get Redux state paths."""
75
- return cast (
76
- dict [str , Any ],
77
- self .driver .execute_script (
78
- """
32
+ def redux_state_paths (self ):
33
+ return self .driver .execute_script (
34
+ """
79
35
var p = window.store.getState().paths;
80
36
return {strs: p.strs, objs: p.objs}
81
37
"""
82
- ),
83
38
)
84
39
85
40
@property
86
- def redux_state_rqs (self ) -> List [Dict [str , Any ]]:
87
- """Get Redux state request queue."""
88
- return cast (
89
- list [dict [str , Any ]],
90
- self .driver .execute_script (
91
- """
41
+ def redux_state_rqs (self ):
42
+ return self .driver .execute_script (
43
+ """
44
+
92
45
// Check for legacy `pendingCallbacks` store prop (compatibility for Dash matrix testing)
93
46
var pendingCallbacks = window.store.getState().pendingCallbacks;
94
47
if (pendingCallbacks) {
@@ -108,62 +61,39 @@ def redux_state_rqs(self) -> List[Dict[str, Any]]:
108
61
109
62
return Array.prototype.concat.apply([], Object.values(callbacksState));
110
63
"""
111
- ),
112
64
)
113
65
114
66
@property
115
- def redux_state_is_loading (self ) -> bool :
116
- """Check if Redux state is loading."""
117
- return cast (
118
- bool ,
119
- self .driver .execute_script (
120
- """
67
+ def redux_state_is_loading (self ):
68
+ return self .driver .execute_script (
69
+ """
121
70
return window.store.getState().isLoading;
122
71
"""
123
- ),
124
72
)
125
73
126
74
@property
127
- def window_store (self ) -> Optional [Any ]:
128
- """Get window.store object."""
75
+ def window_store (self ):
129
76
return self .driver .execute_script ("return window.store" )
130
77
131
- def _wait_for_callbacks (self ) -> bool :
132
- """Check if callbacks are complete."""
133
- # Access properties directly
134
- window_store = self .window_store
135
- redux_state_rqs = self .redux_state_rqs
136
- return (not window_store ) or (redux_state_rqs == [])
137
-
138
- def get_local_storage (self , store_id : str = "local" ) -> Optional [Dict [str , Any ]]:
139
- """Get item from localStorage."""
140
- return cast (
141
- Optional [dict [str , Any ]],
142
- self .driver .execute_script (
143
- f"return JSON.parse(window.localStorage.getItem('{ store_id } '));"
144
- ),
78
+ def _wait_for_callbacks (self ):
79
+ return (not self .window_store ) or self .redux_state_rqs == []
80
+
81
+ def get_local_storage (self , store_id = "local" ):
82
+ return self .driver .execute_script (
83
+ f"return JSON.parse(window.localStorage.getItem('{ store_id } '));"
145
84
)
146
85
147
- def get_session_storage (
148
- self , session_id : str = "session"
149
- ) -> Optional [Dict [str , Any ]]:
150
- """Get item from sessionStorage."""
151
- return cast (
152
- Optional [dict [str , Any ]],
153
- self .driver .execute_script (
154
- f"return JSON.parse(window.sessionStorage.getItem('{ session_id } '));"
155
- ),
86
+ def get_session_storage (self , session_id = "session" ):
87
+ return self .driver .execute_script (
88
+ f"return JSON.parse(window.sessionStorage.getItem('{ session_id } '));"
156
89
)
157
90
158
- def clear_local_storage (self ) -> None :
159
- """Clear localStorage."""
91
+ def clear_local_storage (self ):
160
92
self .driver .execute_script ("window.localStorage.clear()" )
161
93
162
- def clear_session_storage (self ) -> None :
163
- """Clear sessionStorage."""
94
+ def clear_session_storage (self ):
164
95
self .driver .execute_script ("window.sessionStorage.clear()" )
165
96
166
- def clear_storage (self ) -> None :
167
- """Clear both localStorage and sessionStorage."""
97
+ def clear_storage (self ):
168
98
self .clear_local_storage ()
169
99
self .clear_session_storage ()
0 commit comments