Skip to content

Commit 5c416b8

Browse files
authored
Replace Internet Explorer with MSXML in test_eventinterface.py. (#933)
* refactor: Move `gc` and `time` imports to top level in `test_eventinterface.py`. - Relocate `import gc` and `import time` to the top of the file. - Removes redundant inline imports from `tearDown` and test methods. * test: Replace `InternetExplorer` with `MSXML` in `test_eventinterface.py`. - Replaces `InternetExplorer.Application` with `Msxml2.DOMDocument`. - Introduces `IPropertyNotifySink` and `MSXMLDocumentSink` for testing. - Updates test methods to verify default and non-default event interfaces using `MSXML`. * test: Add comments to `IPropertyNotifySink` methods in `test_eventinterface.py`. - Document `OnChanged` and `OnRequestEdit` methods in `IPropertyNotifySink` with descriptions from official documentation to improve code clarity. * test: Document event sink methods in `test_eventinterface.py`. - Add descriptive comments to `IPropertyNotifySink` and `MSXMLDocumentSink` methods. - Clarify whether methods originate from the default dispatch interface or the `IPropertyNotifySink` interface. * test: Add comment explaining `MSXML` usage in `test_eventinterface.py`. - Clarify in `Test_MSXML.setUp` why `Msxml2.DOMDocument` is chosen as the test object, highlighting its support for multiple connection points. * test: Document `GetEvents` usage in `test_eventinterface.py`. - Add comments to `Test_MSXML` methods explaining the behavior of `GetEvents` when connecting to default vs. non-default source interfaces. * test: Document resource cleanup in `test_eventinterface.py`. - Add a comment explaining the use of `gc.collect()` and `time.sleep()` in `Test_MSXML.tearDown` to ensure reliable COM resource release. * test: Document message loop and event expectations in `test_eventinterface.py`. - Clarify message loop processing during event wait. - Document expected event arrivals for default vs. non-default interfaces. * test: Revert the event sink name in `test_eventinterface.py`.
1 parent 069b9d1 commit 5c416b8

1 file changed

Lines changed: 60 additions & 70 deletions

File tree

comtypes/test/test_eventinterface.py

Lines changed: 60 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import gc
2+
import time
13
import unittest as ut
2-
from ctypes import byref
4+
from ctypes import HRESULT, byref
35
from ctypes.wintypes import MSG
46

7+
from comtypes import COMMETHOD, GUID, IUnknown
8+
from comtypes.automation import DISPID
59
from comtypes.client import CreateObject, GetEvents
610
from comtypes.messageloop import (
711
PM_REMOVE,
@@ -10,42 +14,39 @@
1014
TranslateMessage,
1115
)
1216

13-
# FIXME: External test dependencies like this seem bad. Find a different
14-
# built-in win32 API to use.
1517
# The primary goal is to verify how `GetEvents` behaves when the
1618
# `interface` argument is explicitly specified versus when it is omitted,
1719
# using an object that has multiple outgoing event interfaces.
1820

1921

22+
class IPropertyNotifySink(IUnknown):
23+
# https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ipropertynotifysink
24+
_iid_ = GUID("{9BFBBC02-EFF1-101A-84ED-00AA00341D07}")
25+
_methods_ = [
26+
# Called when a property has changed.
27+
COMMETHOD([], HRESULT, "OnChanged", (["in"], DISPID, "dispid")),
28+
# Called when an object wants to know if it's okay to change a property.
29+
COMMETHOD([], HRESULT, "OnRequestEdit", (["in"], DISPID, "dispid")),
30+
]
31+
32+
2033
class EventSink:
2134
def __init__(self):
2235
self._events = []
2336

24-
# some DWebBrowserEvents
25-
def OnVisible(self, this, *args):
26-
# print "OnVisible", args
27-
self._events.append("OnVisible")
28-
29-
def BeforeNavigate(self, this, *args):
30-
# print "BeforeNavigate", args
31-
self._events.append("BeforeNavigate")
37+
# Events from the default dispatch interface
38+
def onreadystatechange(self, this, *args):
39+
self._events.append("onreadystatechange")
3240

33-
def NavigateComplete(self, this, *args):
34-
# print "NavigateComplete", args
35-
self._events.append("NavigateComplete")
41+
def ondataavailable(self, this, *args):
42+
self._events.append("ondataavailable")
3643

37-
# some DWebBrowserEvents2
38-
def BeforeNavigate2(self, this, *args):
39-
# print "BeforeNavigate2", args
40-
self._events.append("BeforeNavigate2")
44+
# Events from `IPropertyNotifySink`
45+
def OnChanged(self, this, *args):
46+
self._events.append("OnChanged")
4147

42-
def NavigateComplete2(self, this, *args):
43-
# print "NavigateComplete2", args
44-
self._events.append("NavigateComplete2")
45-
46-
def DocumentComplete(self, this, *args):
47-
# print "DocumentComplete", args
48-
self._events.append("DocumentComplete")
48+
def OnRequestEdit(self, this, *args):
49+
self._events.append("OnRequestEdit")
4950

5051

5152
def PumpWaitingMessages():
@@ -55,70 +56,59 @@ def PumpWaitingMessages():
5556
DispatchMessage(byref(msg))
5657

5758

58-
class Test(ut.TestCase):
59-
def tearDown(self):
60-
import gc
59+
class Test_MSXML(ut.TestCase):
60+
def setUp(self):
61+
# We use `Msxml2.DOMDocument` because it is a built-in Windows
62+
# component that supports both a default source interface and the
63+
# `IPropertyNotifySink` connection point.
64+
self.doc = CreateObject("Msxml2.DOMDocument")
65+
self.doc.async_ = True
6166

67+
def tearDown(self):
68+
del self.doc
69+
# Force garbage collection and wait slightly to ensure COM resources
70+
# are released properly between tests.
6271
gc.collect()
63-
import time
64-
6572
time.sleep(2)
6673

67-
@ut.skip(
68-
"External test dependencies like this seem bad. Find a different built-in "
69-
"win32 API to use."
70-
)
7174
def test_default_eventinterface(self):
75+
# Verify that `GetEvents` automatically connects to the default source
76+
# interface (dispatch events like `onreadystatechange`) when no
77+
# interface is explicitly requested.
7278
sink = EventSink()
73-
ie = CreateObject("InternetExplorer.Application")
74-
conn = GetEvents(ie, sink=sink)
75-
ie.Visible = True
76-
ie.Navigate2(URL="http://docs.python.org/", Flags=0)
77-
import time
79+
conn = GetEvents(self.doc, sink)
80+
self.doc.loadXML("<root/>")
7881

79-
for i in range(50):
82+
# Give the message loop time to process incoming events.
83+
for _ in range(50):
8084
PumpWaitingMessages()
8185
time.sleep(0.1)
82-
ie.Visible = False
83-
ie.Quit()
84-
85-
self.assertEqual(
86-
sink._events,
87-
[
88-
"OnVisible",
89-
"BeforeNavigate2",
90-
"NavigateComplete2",
91-
"DocumentComplete",
92-
"OnVisible",
93-
],
94-
)
95-
96-
del ie
86+
# Should receive events from the default dispatch interface, but not
87+
# events from `IPropertyNotifySink`.
88+
self.assertIn("onreadystatechange", sink._events)
89+
self.assertNotIn("OnChanged", sink._events)
90+
9791
del conn
9892

99-
@ut.skip(
100-
"External test dependencies like this seem bad. Find a different built-in "
101-
"win32 API to use."
102-
)
10393
def test_nondefault_eventinterface(self):
94+
# Verify that `GetEvents` can connect to a non-default interface
95+
# (like `IPropertyNotifySink`) when it is explicitly provided.
10496
sink = EventSink()
105-
ie = CreateObject("InternetExplorer.Application")
106-
import comtypes.gen.SHDocVw as mod
10797

108-
conn = GetEvents(ie, sink, interface=mod.DWebBrowserEvents)
98+
conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink)
10999

110-
ie.Visible = True
111-
ie.Navigate2(Flags=0, URL="http://docs.python.org/")
112-
import time
100+
self.doc.loadXML("<root/>")
113101

114-
for i in range(50):
102+
# Give the message loop time to process incoming events.
103+
for _ in range(50):
115104
PumpWaitingMessages()
116105
time.sleep(0.1)
117-
ie.Visible = False
118-
ie.Quit()
106+
# Should receive events from `IPropertyNotifySink`, but not events from
107+
# the default dispatch interface.
108+
self.assertNotIn("onreadystatechange", sink._events)
109+
self.assertIn("OnChanged", sink._events)
119110

120-
self.assertEqual(sink._events, ["BeforeNavigate", "NavigateComplete"])
121-
del ie
111+
del conn
122112

123113

124114
if __name__ == "__main__":

0 commit comments

Comments
 (0)