Skip to content

Commit fc6d35e

Browse files
committed
[tests] Capture browser console logs in Firefox
Firefox does not support the WebDriver.get_log API. To work around this, we inject JavaScript into the page to override window.console within the browser's JS runtime. This allows us to capture and retrieve console errors directly from the page.
1 parent 3cce4be commit fc6d35e

File tree

4 files changed

+83
-10
lines changed

4 files changed

+83
-10
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
(function () {
2+
const script = document.createElement("script");
3+
script.textContent = `
4+
(function() {
5+
if (window._console_logs) return; // Prevent multiple injections
6+
7+
window._console_logs = [];
8+
9+
function mapLogLevel(method) {
10+
const levelMapping = {
11+
log: "INFO",
12+
info: "INFO",
13+
debug: "DEBUG",
14+
warn: "WARNING",
15+
error: "SEVERE",
16+
trace: "DEBUG",
17+
assert: "SEVERE"
18+
};
19+
return levelMapping[method] || "INFO"; // Default to INFO
20+
}
21+
22+
function captureConsole(method) {
23+
const original = console[method];
24+
console[method] = function(...args) {
25+
const logLevel = mapLogLevel(method);
26+
window._console_logs.push({
27+
level: logLevel,
28+
message: args.map(arg => (typeof arg === "object" ? JSON.stringify(arg) : String(arg))).join(" ")
29+
});
30+
original.apply(console, args);
31+
};
32+
}
33+
34+
const methods = ["log", "info", "debug", "warn", "error", "trace", "assert"];
35+
methods.forEach(captureConsole);
36+
})();
37+
`;
38+
39+
// Ensure script runs in page context
40+
const head = document.head || document.documentElement;
41+
head.appendChild(script);
42+
})();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "Console Capture Extension",
4+
"version": "1.0",
5+
"description": "Captures console logs before any script runs.",
6+
"content_scripts": [
7+
{
8+
"matches": ["<all_urls>"],
9+
"js": ["content.js"],
10+
"run_at": "document_start"
11+
}
12+
]
13+
}

openwisp_utils/test_selenium_mixins.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,29 @@ def setUpClass(cls):
4444
if GECKO_LOG:
4545
kwargs['service'] = webdriver.FirefoxService(log_output='geckodriver.log')
4646
cls.web_driver = webdriver.Firefox(**kwargs)
47+
# Firefox does not support the WebDriver.get_log API. To work around this,
48+
# we inject JavaScript into the page to override window.console within the
49+
# browser's JS runtime. This allows us to capture and retrieve console errors
50+
# directly from the page.
51+
extension_path = os.path.abspath(
52+
os.path.join(
53+
os.path.dirname(__file__),
54+
"firefox-extensions",
55+
"console_capture_extension",
56+
)
57+
)
58+
cls.web_driver.install_addon(extension_path, temporary=True)
4759

4860
@classmethod
4961
def tearDownClass(cls):
5062
cls.web_driver.quit()
5163
super().tearDownClass()
5264

65+
def setUp(self):
66+
self.admin = self._create_admin(
67+
username=self.admin_username, password=self.admin_password
68+
)
69+
5370
def open(self, url, driver=None, timeout=5):
5471
"""Opens a URL.
5572
@@ -68,6 +85,9 @@ def open(self, url, driver=None, timeout=5):
6885
EC.presence_of_element_located((By.CSS_SELECTOR, '#main-content'))
6986
)
7087

88+
def get_browser_logs(self):
89+
return self.web_driver.execute_script('return window._console_logs')
90+
7191
def login(self, username=None, password=None, driver=None):
7292
"""Log in to the admin dashboard.
7393
@@ -96,6 +116,11 @@ def find_element(self, by, value, timeout=2, wait_for='visibility'):
96116
getattr(self, method)(by, value, timeout)
97117
return self.web_driver.find_element(by=by, value=value)
98118

119+
def find_elements(self, by, value, timeout=2, wait_for='visibility'):
120+
method = f'wait_for_{wait_for}'
121+
getattr(self, method)(by, value, timeout)
122+
return self.web_driver.find_elements(by=by, value=value)
123+
99124
def wait_for_visibility(self, by, value, timeout=2):
100125
return self.wait_for('visibility_of_element_located', by, value)
101126

@@ -111,4 +136,5 @@ def wait_for(self, method, by, value, timeout=2):
111136
getattr(EC, method)(((by, value)))
112137
)
113138
except TimeoutException as e:
139+
print(self.get_browser_logs())
114140
self.fail(f'{method} of "{value}" failed: {e}')

tests/test_project/tests/test_selenium.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010

1111

1212
class TestMenu(SeleniumTestMixin, StaticLiveServerTestCase):
13-
def setUp(self):
14-
self.admin = self._create_admin()
15-
1613
def tearDown(self):
1714
# Clear local storage
1815
self.web_driver.execute_script('window.localStorage.clear()')
@@ -369,7 +366,7 @@ def setUpClass(cls):
369366
super().setUpClass()
370367

371368
def setUp(self):
372-
self.admin = self._create_admin()
369+
super().setUp()
373370
self.web_driver.set_window_size(1600, 768)
374371
self._create_test_data()
375372

@@ -516,10 +513,6 @@ class TestInputFilters(SeleniumTestMixin, CreateMixin, StaticLiveServerTestCase)
516513
def setUpClass(cls):
517514
super().setUpClass()
518515

519-
def setUp(self):
520-
super().setUp()
521-
self.admin = self._create_admin()
522-
523516
def test_input_filters(self):
524517
url = reverse('admin:test_project_shelf_changelist')
525518
user = self._create_user()
@@ -613,7 +606,7 @@ def setUpClass(cls):
613606
super().setUpClass()
614607

615608
def setUp(self):
616-
self.admin = self._create_admin()
609+
super().setUp()
617610
self.web_driver.set_window_size(1600, 768)
618611

619612
def test_pie_chart_zero_annotation(self):
@@ -639,7 +632,6 @@ def setUpClass(cls):
639632

640633
def setUp(self):
641634
super().setUp()
642-
self.admin = self._create_admin()
643635
self.login()
644636

645637
def test_autocomplete_shelf_filter(self):

0 commit comments

Comments
 (0)