@@ -98,7 +98,7 @@ class TestStealthDetection:
9898 """
9999
100100 @pytest .fixture (autouse = True )
101- def set_tmpdir (self , tmp_path : Path ):
101+ def set_tmpdir (self , tmp_path : Path ) -> None :
102102 self .tmpdir = tmp_path
103103
104104 def _get_stealth_config (
@@ -140,60 +140,61 @@ def test_stealth_passes_all_detection_checks(self):
140140 assert results , "No detection results collected"
141141
142142 # navigator.webdriver should be hidden
143- assert results . get ( "webdriver_flag" ) is True , (
144- "navigator.webdriver was not hidden by stealth extension"
145- )
143+ assert (
144+ results . get ( "webdriver_flag" ) is True
145+ ), "navigator.webdriver was not hidden by stealth extension"
146146
147147 # Canvas functions should still appear native (no prototype pollution)
148- assert results . get ( "canvas_functions_native" ) is True , (
149- "Canvas functions are not native - stealth instrument should avoid prototype pollution"
150- )
148+ assert (
149+ results . get ( "canvas_functions_native" ) is True
150+ ), "Canvas functions are not native - stealth instrument should avoid prototype pollution"
151151
152152 # Storage functions should still appear native
153- assert results . get ( "storage_functions_native" ) is True , (
154- "Storage functions are not native - stealth instrument should avoid prototype pollution"
155- )
153+ assert (
154+ results . get ( "storage_functions_native" ) is True
155+ ), "Storage functions are not native - stealth instrument should avoid prototype pollution"
156156
157157 # Navigator getters should appear native
158- assert results . get ( "navigator_native" ) is True , (
159- "Navigator getters are not native - stealth instrument should avoid prototype pollution"
160- )
158+ assert (
159+ results . get ( "navigator_native" ) is True
160+ ), "Navigator getters are not native - stealth instrument should avoid prototype pollution"
161161
162162 # No OpenWPM globals should be exposed
163- assert results . get ( "no_global_leaks" ) is True , (
164- "OpenWPM globals detected (jsInstruments, instrumentFingerprintingApis, etc.)"
165- )
163+ assert (
164+ results . get ( "no_global_leaks" ) is True
165+ ), "OpenWPM globals detected (jsInstruments, instrumentFingerprintingApis, etc.)"
166166
167167 # Constructor properties should be preserved
168- assert results . get ( "constructors_present" ) is True , (
169- "Constructor properties missing on instrumented objects"
170- )
168+ assert (
169+ results . get ( "constructors_present" ) is True
170+ ), "Constructor properties missing on instrumented objects"
171171
172172 # Function.prototype.bind should not be tampered
173- assert results . get ( "bind_integrity" ) is True , (
174- "Function.prototype.bind integrity check failed"
175- )
173+ assert (
174+ results . get ( "bind_integrity" ) is True
175+ ), "Function.prototype.bind integrity check failed"
176176
177177 # Error stacks should not contain extension URLs
178- assert results . get ( "clean_error_stacks" ) is True , (
179- "Error stack traces contain moz-extension:// URLs"
180- )
178+ assert (
179+ results . get ( "clean_error_stacks" ) is True
180+ ), "Error stack traces contain moz-extension:// URLs"
181181
182182 # No extra properties added to prototypes
183- assert results . get ( "no_extra_prototype_properties" ) is True , (
184- "Extra instrumentation properties found on prototypes"
185- )
183+ assert (
184+ results . get ( "no_extra_prototype_properties" ) is True
185+ ), "Extra instrumentation properties found on prototypes"
186186
187187 # RTC functions should appear native
188- assert results . get ( "rtc_native" ) is True , (
189- "RTCPeerConnection functions are not native"
190- )
188+ assert (
189+ results . get ( "rtc_native" ) is True
190+ ), "RTCPeerConnection functions are not native"
191191
192192 def test_stealth_records_js_calls (self ):
193193 """Verify that the stealth extension actually records JS instrumentation data.
194194
195195 The detection tests above check that stealth is undetectable, but we also
196- need to confirm it is actually capturing API calls to the database.
196+ need to confirm it is actually capturing API calls to the database
197+ with meaningful data (symbols, operations, values).
197198 """
198199 manager_params , browser_params = self ._get_stealth_config ()
199200 db_path = manager_params .data_directory / "crawl-data.sqlite"
@@ -219,6 +220,43 @@ def test_stealth_records_js_calls(self):
219220 "in the javascript table"
220221 )
221222
223+ # Verify data quality: rows should have non-empty symbols and operations
224+ symbols = {row ["symbol" ] for row in rows }
225+ operations = {row ["operation" ] for row in rows }
226+ assert len (symbols ) > 0 , "No symbols recorded in JS instrumentation data"
227+ assert len (operations ) > 0 , "No operations recorded in JS instrumentation data"
228+
229+ # At least some rows should have non-empty values (proving data capture works)
230+ rows_with_values = [r for r in rows if r ["value" ] and r ["value" ] != "" ]
231+ assert len (rows_with_values ) > 0 , (
232+ "Stealth extension recorded JS calls but no values — "
233+ "error handling may be discarding data"
234+ )
235+
236+ def test_legacy_records_with_call_stacks (self ):
237+ """Verify that legacy JS instrumentation records calls with full stack traces.
238+
239+ This serves as a baseline comparison: legacy mode captures call stacks
240+ that include extension URLs (which stealth intentionally sanitizes).
241+ """
242+ manager_params , browser_params = self ._get_legacy_config ()
243+ db_path = manager_params .data_directory / "crawl-data.sqlite"
244+ structured_provider = SQLiteStorageProvider (db_path )
245+ manager = TaskManager (
246+ manager_params ,
247+ browser_params ,
248+ structured_provider ,
249+ None ,
250+ )
251+
252+ cs = CommandSequence (STEALTH_DETECTION_URL )
253+ cs .get (sleep = 2 )
254+ manager .execute_command_sequence (cs )
255+ manager .close ()
256+
257+ rows = db_utils .get_javascript_entries (db_path , all_columns = True )
258+ assert len (rows ) > 0 , "Legacy JS instrumentation did not record any data"
259+
222260 def test_legacy_instrument_is_detectable (self ):
223261 """With legacy JS instrumentation, detection checks should catch it.
224262
0 commit comments