From 52c993e88acbe1925798d2ce7c3b0945a2db1e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Deuerling?= Date: Fri, 24 Jan 2025 14:07:17 +0100 Subject: [PATCH] feat: Make Playwright host configurable --- Browser/browser.py | 4 ++++ Browser/playwright.py | 15 ++++++++++----- node/playwright-wrapper/index.ts | 18 ++++++++++++++---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Browser/browser.py b/Browser/browser.py index 86eff870c..3f6417547 100755 --- a/Browser/browser.py +++ b/Browser/browser.py @@ -804,6 +804,7 @@ def __init__( # noqa: PLR0915 external_browser_executable: Optional[dict[SupportedBrowsers, str]] = None, jsextension: Union[list[str], str, None] = None, language: Optional[str] = None, + playwright_process_host: Optional[str] = None, playwright_process_port: Optional[int] = None, plugins: Union[list[str], str, None] = None, retry_assertions_for: timedelta = timedelta(seconds=1), @@ -824,6 +825,7 @@ def __init__( # noqa: PLR0915 | ``external_browser_executable`` | Dict mapping name of browser to path of executable of a browser. Will make opening new browsers of the given type use the set executablePath. Currently only configuring of `chromium` to a separate executable (chrome, chromium and Edge executables all work with recent versions) works. | | ``jsextension`` | Path to Javascript modules exposed as extra keywords. The modules must be in CommonJS. It can either be a single path, a comma-separated lists of path or a real list of strings | | ``language`` | Defines language which is used to translate keyword names and documentation. | + | ``playwright_process_host`` | Hostname / Host address which should be used when spawning the Playwright process. Defaults to 127.0.0.1. | | ``playwright_process_port`` | Experimental reusing of playwright process. ``playwright_process_port`` is preferred over environment variable ``ROBOT_FRAMEWORK_BROWSER_NODE_PORT``. See `Experimental: Re-using same node process` for more details. | | ``plugins`` | Allows extending the Browser library with external Python classes. Can either be a single class/module, a comma-separated list or a real list of strings | | ``retry_assertions_for`` | Timeout for retrying assertions on keywords before failing the keywords. This timeout starts counting from the first failure. Global ``timeout`` will still be in effect. This allows stopping execution faster to assertion failure when element is found fast. | @@ -865,6 +867,7 @@ def __init__( # noqa: PLR0915 WebAppState(self), ] self.enable_playwright_debug = enable_playwright_debug + self.playwright_process_host = playwright_process_host self.playwright_process_port = playwright_process_port if self.enable_playwright_debug is True: self.enable_playwright_debug = PlaywrightLogTypes.playwright @@ -935,6 +938,7 @@ def playwright(self) -> Playwright: self._playwright = Playwright( self, self.enable_playwright_debug, + self.playwright_process_host, self.playwright_process_port, self._playwright_log, ) diff --git a/Browser/playwright.py b/Browser/playwright.py index 8e1f0b490..a378d5f77 100644 --- a/Browser/playwright.py +++ b/Browser/playwright.py @@ -45,12 +45,14 @@ def __init__( self, library: "Browser", enable_playwright_debug: PlaywrightLogTypes, + host: Optional[str] = None, port: Optional[int] = None, playwright_log: Union[Path, None] = Path(Path.cwd()), ): LibraryComponent.__init__(self, library) self.enable_playwright_debug = enable_playwright_debug self.ensure_node_dependencies() + self.host = str(host) if host else None self.port = str(port) if port else None self.playwright_log = playwright_log @@ -107,10 +109,12 @@ def start_playwright(self) -> Optional[Popen]: logfile = self.playwright_log.open("w") else: logfile = Path(os.devnull).open("w") # noqa: SIM115 + host = str(self.host) if self.host is not None else "127.0.0.1" port = str(find_free_port()) if self.enable_playwright_debug == PlaywrightLogTypes.playwright: os.environ["DEBUG"] = "pw:api" - logger.info(f"Starting Browser process {playwright_script} using port {port}") + logger.info(f"Starting Browser process {playwright_script} using at {host}:{port}") + self.host = host self.port = port node_args = ["node"] node_debug_options = os.environ.get( @@ -119,6 +123,7 @@ def start_playwright(self) -> Optional[Popen]: if node_debug_options: node_args.extend(node_debug_options.split(",")) node_args.append(str(playwright_script)) + node_args.append(host) node_args.append(port) if not os.environ.get("PLAYWRIGHT_BROWSERS_PATH"): os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "0" @@ -134,24 +139,24 @@ def start_playwright(self) -> Optional[Popen]: def wait_until_server_up(self): for _ in range(50): - with grpc.insecure_channel(f"127.0.0.1:{self.port}") as channel: + with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: try: stub = playwright_pb2_grpc.PlaywrightStub(channel) response = stub.Health(Request().Empty()) logger.debug( - f"Connected to the playwright process at port {self.port}: {response}" + f"Connected to the playwright process at {self.host}:{self.port}: {response}" ) return except grpc.RpcError as err: logger.debug(err) time.sleep(0.1) raise RuntimeError( - f"Could not connect to the playwright process at port {self.port}." + f"Could not connect to the playwright process at {self.host}:{self.port}." ) @cached_property def _channel(self): - return grpc.insecure_channel(f"127.0.0.1:{self.port}") + return grpc.insecure_channel(f"{self.host}:{self.port}") @contextlib.contextmanager def grpc_channel(self, original_error=False): diff --git a/node/playwright-wrapper/index.ts b/node/playwright-wrapper/index.ts index 46c4fc237..c4a634722 100644 --- a/node/playwright-wrapper/index.ts +++ b/node/playwright-wrapper/index.ts @@ -19,16 +19,26 @@ import { PlaywrightService } from './generated/playwright_grpc_pb'; import { pino } from 'pino'; const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime }); -const port = process.argv.slice(2); -if (Object.keys(port).length == 0) { +const args = process.argv.slice(2); + +const host = args[0]; +const port = args[1]; + +if (!host) { + throw new Error(`No host defined`); +} + +if (!port) { throw new Error(`No port defined`); } + const server = new Server(); server.addService( PlaywrightService as unknown as ServiceDefinition, new PlaywrightServer() as unknown as UntypedServiceImplementation, ); -server.bindAsync(`127.0.0.1:${port}`, ServerCredentials.createInsecure(), () => { - logger.info(`Listening on ${port}`); + +server.bindAsync(`${host}:${port}`, ServerCredentials.createInsecure(), () => { + logger.info(`Listening on ${host}:${port}`); server.start(); });