Merge pull request #38399 from iamejaaz/ui-pdf-debugging

feat(PrintFormat): UI pdf debugging
This commit is contained in:
Ejaaz Khan 2026-04-04 17:29:30 +05:30 committed by GitHub
commit f423bf4979
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 29 additions and 5 deletions

View file

@ -3,6 +3,7 @@ from typing import ClassVar
from bs4 import BeautifulSoup
import frappe
from frappe.utils.data import cint
from frappe.utils.pdf import get_host_url
from frappe.utils.print_utils import convert_uom, parse_float_and_unit
@ -10,6 +11,7 @@ from frappe.utils.print_utils import convert_uom, parse_float_and_unit
class Browser:
def __init__(self, generator, print_format, html, options):
self.is_print_designer = frappe.get_cached_value("Print Format", print_format, "print_designer")
self.debug_mode = frappe.conf.developer_mode and bool(frappe.form_dict.get("pdf_debug"))
self.browserID = frappe.utils.random_string(10)
generator.add_browser(self.browserID)
# sets soup from html
@ -31,7 +33,8 @@ class Browser:
# now wait for page to load as we need DOM to generate pdf
self.body_page.wait_for_set_content()
self.body_pdf = self.body_page.generate_pdf(raw=not self.header_page and not self.footer_page)
self.body_page.close()
if not self.debug_mode:
self.body_page.close()
self.update_header_footer_page()
if self.header_page:
@ -39,18 +42,23 @@ class Browser:
self.header_pdf = self.header_page.get_pdf_from_stream(self.header_page.get_pdf_stream_id())
else:
self.header_pdf = self.header_page.generate_pdf()
self.header_page.close()
if not self.debug_mode:
self.header_page.close()
if self.footer_page:
if not self.is_footer_dynamic:
self.footer_pdf = self.footer_page.get_pdf_from_stream(self.footer_page.get_pdf_stream_id())
else:
self.footer_pdf = self.footer_page.generate_pdf()
self.footer_page.close()
if not self.debug_mode:
self.footer_page.close()
self.close()
if not self.debug_mode:
self.close()
generator.remove_browser(self.browserID)
if self.debug_mode:
generator.detach_debug_browser()
def open(self, generator):
from frappe.utils.pdf_generator.cdp_connection import CDPSocketClient

View file

@ -9,6 +9,7 @@ import requests
import frappe
from frappe import _
from frappe.utils.data import cint
from frappe.utils.print_utils import find_or_download_chromium_executable
# TODO: close browser when worker is killed.
@ -69,11 +70,13 @@ class ChromePDFGenerator:
self.USE_PERSISTENT_CHROMIUM = site_config.get("use_persistent_chromium", False)
# time to wait for chromium to start and provide dev tools url used in _set_devtools_url.
self.START_TIMEOUT = site_config.get("chromium_start_timeout", 3)
# Allow a single PDF request to opt into interactive Chromium debugging in developer mode only.
self.debug_mode = frappe.conf.developer_mode and bool(frappe.form_dict.get("pdf_debug"))
self._chromium_path = find_or_download_chromium_executable()
if self._verify_chromium_installation():
if not self._devtools_url:
self.start_chromium_process()
self.start_chromium_process(debug=self.debug_mode)
def _verify_chromium_installation(self):
"""Ensures Chromium is available and executable, raising clearer errors if not."""
@ -231,6 +234,19 @@ class ChromePDFGenerator:
self._devtools_url = None
frappe.log("Headless Chromium closed successfully.")
def detach_debug_browser(self):
"""
Detach the generator from an interactive debug Chromium process.
This keeps the debug browser window available for inspection, while ensuring
the next PDF request starts with a fresh generator/process instead of reusing
the old debug session.
"""
ChromePDFGenerator._instance = None
self._initialized = False
self._chromium_process = None
self._devtools_url = None
# not used anywhere in the code. read _set_devtools_url for more info. useful in case we want to take different approch to fetch devtools url.
def fetch_devtools_url(self, port):
if not port: