Skip to content

Commit 4451950

Browse files
Version 3.4.2 (#107)
1 parent 4ae770d commit 4451950

6 files changed

Lines changed: 109 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@
22

33
<br>
44

5-
## v3.4.1
5+
## v3.4.2
6+
*Release date: 2026-03-28*
7+
8+
### Fixed
9+
- `ShadowDriverWrapper` now correctly receives static methods of its driver type — previously methods from the first session's driver were inherited and not overridden
10+
- `get_driver_instance` cache key changed from driver instance to driver type — prevents cache misses on every new driver object
11+
12+
### Changed
13+
- `_set_static` guard stores the configured class instead of `True` — allows re-configuration when driver type changes
14+
- `_set_static` uses `_framework_attrs` snapshot instead of full MRO scan — protects only original framework methods, not previously set driver-specific ones
15+
16+
---
17+
18+
## v3.4.1
619
*Release date: 2026-03-27*
720

821
### Fixed

mops/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = '3.4.1'
1+
__version__ = '3.4.2'
22
__project_name__ = 'mops'

mops/base/driver_wrapper.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def __new__(cls, *args, **kwargs):
128128
if cls.session.sessions_count() == 0:
129129
cls = super().__new__(cls)
130130
else:
131-
cls = super().__new__(type(f'ShadowDriverWrapper', (cls, ), get_attributes_from_object(cls))) # noqa
131+
attrs = get_attributes_from_object(cls)
132+
attrs.pop('_configured', None)
133+
cls = super().__new__(type(f'ShadowDriverWrapper', (cls, ), attrs)) # noqa
132134

133135
for name, _ in extract_named_objects(cls, bool).items():
134136
setattr(cls, name, False)

mops/mixins/internal_mixin.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from mops.utils.internal_utils import (
77
extract_named_objects,
88
extract_all_named_objects,
9+
is_driver_wrapper,
910
)
1011

1112

@@ -49,6 +50,15 @@ def _safe_setter(self, var: str, value: Any):
4950
if not hasattr(self, var):
5051
setattr(self, var, value)
5152

53+
def _get_protected_attrs(self: Any, current_obj_cls) -> set:
54+
if not is_driver_wrapper(self):
55+
return set(get_all_static_attributes(current_obj_cls))
56+
57+
if '_framework_attrs' not in current_obj_cls.__dict__:
58+
current_obj_cls._framework_attrs = set(get_all_static_attributes(current_obj_cls))
59+
60+
return current_obj_cls.__dict__['_framework_attrs']
61+
5262
def _set_static(self: Any, cls) -> None:
5363
"""
5464
Set static from base cls (Web/Mobile/Play Element/Page etc.)
@@ -57,16 +67,16 @@ def _set_static(self: Any, cls) -> None:
5767
"""
5868
current_obj_cls = self.__class__
5969

60-
if current_obj_cls.__dict__.get('_configured'):
70+
if current_obj_cls.__dict__.get('_configured') is cls:
6171
return
6272

63-
existing_attrs = set(get_all_static_attributes(current_obj_cls))
73+
protected = self._get_protected_attrs(current_obj_cls)
6474

6575
for name, value in get_static_attributes(cls).items():
66-
if name not in existing_attrs:
76+
if name not in protected:
6777
setattr(current_obj_cls, name, value)
6878

69-
current_obj_cls._configured = True
79+
current_obj_cls._configured = cls
7080

7181
def _repr_builder(self: Any):
7282
class_name = self.__class__.__name__

tests/static_tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ def base_teardown():
154154
CoreDriver.driver = None
155155
DriverWrapperSessions.all_sessions = []
156156

157+
if '_framework_attrs' in MockedDriverWrapper.__dict__:
158+
framework_attrs = MockedDriverWrapper.__dict__['_framework_attrs']
159+
for attr in list(MockedDriverWrapper.__dict__.keys()):
160+
if not attr.startswith('_') and attr not in framework_attrs:
161+
try:
162+
delattr(MockedDriverWrapper, attr)
163+
except AttributeError:
164+
pass
165+
del MockedDriverWrapper._framework_attrs
166+
if '_configured' in MockedDriverWrapper.__dict__:
167+
del MockedDriverWrapper._configured
168+
157169

158170
mobile_drivers = [mocked_ios_driver.__name__, mocked_android_driver.__name__]
159171
mobile_ids = ['appium ios', 'appium android']
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import inspect
2+
3+
from mops.base.driver_wrapper import DriverWrapper
4+
from mops.playwright.play_driver import PlayDriver
5+
from mops.selenium.core.core_driver import CoreDriver
6+
from mops.selenium.driver.mobile_driver import MobileDriver
7+
from mops.selenium.driver.web_driver import WebDriver
8+
from mops.utils.internal_utils import get_attributes_from_object
9+
10+
11+
def _own_methods(cls):
12+
return {
13+
name for name, val in get_attributes_from_object(cls).items()
14+
if not name.startswith('_') and callable(val)
15+
}
16+
17+
18+
def _assert_from(dw, methods, source_cls):
19+
for name in methods:
20+
raw = inspect.getattr_static(dw, name, None)
21+
assert raw is not None, f"'{name}' not found on {type(dw).__name__}"
22+
func = raw.__func__ if isinstance(raw, (classmethod, staticmethod)) else raw
23+
qualname = getattr(func, '__qualname__', '')
24+
assert source_cls.__name__ in qualname, (
25+
f"'{name}' on {type(dw).__name__} expected from {source_cls.__name__}, "
26+
f"got {qualname!r}"
27+
)
28+
29+
30+
_WRAPPER = _own_methods(DriverWrapper)
31+
_WEB = _own_methods(WebDriver)
32+
_MOBILE = _own_methods(MobileDriver)
33+
_CORE = _own_methods(CoreDriver)
34+
_PLAY = _own_methods(PlayDriver)
35+
36+
37+
def _assert_android_attrs(dw):
38+
_assert_from(dw, _MOBILE, MobileDriver)
39+
_assert_from(dw, _CORE - _MOBILE - _WRAPPER, CoreDriver)
40+
_assert_from(dw, _WRAPPER, DriverWrapper)
41+
42+
43+
def _assert_selenium_attrs(dw):
44+
_assert_from(dw, _WEB, WebDriver)
45+
_assert_from(dw, _CORE - _WEB - _WRAPPER, CoreDriver)
46+
_assert_from(dw, _WRAPPER, DriverWrapper)
47+
48+
49+
def _assert_playwright_attrs(dw):
50+
_assert_from(dw, _PLAY - _WRAPPER, PlayDriver)
51+
_assert_from(dw, _WRAPPER, DriverWrapper)
52+
53+
54+
def test_android_and_selenium_attrs(mocked_android_driver, mocked_selenium_driver):
55+
assert 'Shadow' not in type(mocked_android_driver).__name__
56+
assert type(mocked_selenium_driver).__name__ == 'ShadowDriverWrapper'
57+
_assert_android_attrs(mocked_android_driver)
58+
_assert_selenium_attrs(mocked_selenium_driver)
59+
60+
61+
def test_android_and_playwright_attrs(mocked_android_driver, mocked_play_driver):
62+
assert 'Shadow' not in type(mocked_android_driver).__name__
63+
assert type(mocked_play_driver).__name__ == 'ShadowDriverWrapper'
64+
_assert_android_attrs(mocked_android_driver)
65+
_assert_playwright_attrs(mocked_play_driver)

0 commit comments

Comments
 (0)