Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Browser/entry/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

import click

from Browser.utils.data_types import InstallableBrowser, InstallationOptions

from .constant import (
INSTALLATION_DIR,
NODE_MODULES,
Expand Down Expand Up @@ -366,6 +368,36 @@ def convert_options_types(options: list[str], browser_lib: "Browser"):
return params


@cli.command()
@click.argument("browser", type=click.Choice([b.value for b in InstallableBrowser]), required=False, default=None)
def install_browser(browser: Optional[str] = None, **flags):
"""Install Playwright Browsers.

It installs the specified browser by executing 'npx playwright install' command.
All installation options are passed to the command.
"""
browser_enum = browser if browser is None else InstallableBrowser(browser)
selected = []
for name, enabled in flags.items():
if enabled:
key = name.replace("_", "-") # e.g. with_deps -> with-deps
selected.append(InstallationOptions[key])
from ..browser import Browser, PlaywrightLogTypes # noqa: PLC0415
from ..playwright import Playwright # noqa: PLC0415
os.environ["PINO_LOG_LEVEL"] = "error"
browser_lib = Browser()
browser_lib._playwright = Playwright(
library=browser_lib,
enable_playwright_debug=PlaywrightLogTypes.library,
playwright_log=sys.stdout,
)
with contextlib.suppress(Exception):
browser_lib.install_browser(browser_enum, *selected)
for opt in InstallationOptions:
param_name = opt.name.replace("-", "_")
install_browser = click.option(opt.value, param_name, is_flag=True, help=opt.name)(install_browser)


@cli.command()
@click.argument(
"path",
Expand Down
21 changes: 21 additions & 0 deletions Browser/keywords/browser_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
from enum import Enum
import json
import uuid
from collections.abc import Iterable
Expand All @@ -23,6 +24,8 @@
from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
from robot.utils import get_link_path

from Browser.utils.data_types import InstallableBrowser, InstallationOptions

from ..base import LibraryComponent
from ..generated.playwright_pb2 import Request
from ..utils import (
Expand Down Expand Up @@ -633,3 +636,21 @@ def clear_permissions(self):
with self.playwright.grpc_channel() as stub:
response = stub.ClearPermissions(Request().Empty())
logger.info(response.log)

@keyword
def install_browser(self, browser: Optional[InstallableBrowser] = None, *options: InstallationOptions):
"""Executes a Playwright command with the given arguments."""
with self.playwright.grpc_channel() as stub:
try:
response = stub.ExecutePlaywright(
Request().Json(
body=json.dumps(['install']
+ [opt.value for opt in options]
+ ([browser.value] if browser else [])
)
)
)
logger.info(response.log)
except Exception:
logger.error("Error executing Playwright command")

17 changes: 10 additions & 7 deletions Browser/playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from functools import cached_property
from pathlib import Path
from subprocess import DEVNULL, STDOUT, CalledProcessError, Popen, run
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Optional, TextIO, Union

import grpc # type: ignore

Expand All @@ -47,7 +47,7 @@ def __init__(
enable_playwright_debug: Union[PlaywrightLogTypes, bool],
host: Optional[str] = None,
port: Optional[int] = None,
playwright_log: Union[Path, None] = Path(Path.cwd()),
playwright_log: Optional[Union[Path, TextIO]] = Path(Path.cwd()),
):
LibraryComponent.__init__(self, library)
self.enable_playwright_debug = enable_playwright_debug
Expand Down Expand Up @@ -98,6 +98,13 @@ def ensure_node_dependencies(self):
f"\nInstallation path: {installation_dir}"
)

def _get_logfile(self) -> TextIO:
if isinstance(self.playwright_log, Path):
return self.playwright_log.open("w", encoding="utf-8")
if self.playwright_log is None:
return Path(os.devnull).open("w", encoding="utf-8")
return self.playwright_log

def start_playwright(self) -> Optional[Popen]:
env_node_port = os.environ.get("ROBOT_FRAMEWORK_BROWSER_NODE_PORT")
existing_port = self.port or env_node_port
Expand All @@ -117,10 +124,6 @@ def start_playwright(self) -> Optional[Popen]:
current_dir = Path(__file__).parent
workdir = current_dir / "wrapper"
playwright_script = workdir / "index.js"
if self.playwright_log:
logfile = self.playwright_log.open("w", encoding="utf-8")
else:
logfile = Path(os.devnull).open("w", encoding="utf-8") # 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:
Expand All @@ -147,7 +150,7 @@ def start_playwright(self) -> Optional[Popen]:
shell=False,
cwd=workdir,
env=os.environ,
stdout=logfile,
stdout=self._get_logfile(),
stderr=STDOUT,
)

Expand Down
2 changes: 2 additions & 0 deletions Browser/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
HighLightElement,
HighlightMode,
HttpCredentials,
InstallableBrowser,
InstallationOptions,
LambdaFunction,
NewPageDetails,
Media,
Expand Down
33 changes: 33 additions & 0 deletions Browser/utils/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1394,3 +1394,36 @@ class TracingGroupMode(Enum):
Full = auto()
Browser = auto()
Playwright = auto()


InstallableBrowser = Enum("InstallableBrowser", {
"chromium": "chromium",
"firefox": "firefox",
"webkit": "webkit",
"chromium-headless-shell": "chromium-headless-shell",
"chromium-tip-of-tree-headless-shell": "chromium-tip-of-tree-headless-shell",
"chrome": "chrome",
"chrome-beta": "chrome-beta",
"msedge": "msedge",
"msedge-beta": "msedge-beta",
"msedge-dev": "msedge-dev",
})
InstallableBrowser.__doc__ = """Enum of browsers that can be installed with `Install Browser` keyword."""


InstallationOptions = Enum("InstallationOptions", {
"with-deps": "--with-deps",
"dry-run": "--dry-run",
"list": "--list",
"force": "--force",
"only-shell": "--only-shell",
"no-shell": "--no-shell",
})
InstallationOptions.__doc__ = """Enum of installation options for `Install Browser` keyword.

- with-deps install system dependencies for browsers
- dry-run do not execute installation, only print information
- list prints list of browsers from all playwright installations
- force force reinstall of stable browser channels
- only-shell only install headless shell when installing chromium
- no-shell do not install chromium headless shell"""
3 changes: 3 additions & 0 deletions node/build.wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ esbuild.build(
platform: "node",
outfile: "./Browser/wrapper/index.js",
plugins: [nodeExternalsPlugin()],
external: [
'playwright-core/*',
],
}
).catch(() => process.exit(1));
10 changes: 10 additions & 0 deletions node/playwright-wrapper/browser-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@

const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime });

const { program: pwProgram } =

Check failure on line 25 in node/playwright-wrapper/browser-control.ts

View workflow job for this annotation

GitHub Actions / testing (windows-latest, 3.10, 22.x, 1)

A `require()` style import is forbidden

Check failure on line 25 in node/playwright-wrapper/browser-control.ts

View workflow job for this annotation

GitHub Actions / testing (macos-latest, 3.11, 20.x, 1)

A `require()` style import is forbidden

Check failure on line 25 in node/playwright-wrapper/browser-control.ts

View workflow job for this annotation

GitHub Actions / testing (windows-latest, 3.9, 20.x, 1)

A `require()` style import is forbidden
require('playwright-core/lib/cli/program') as { program: import('commander').Command };

export async function executePlaywright(request: Request.Json): Promise<Response.Empty> {
const args = JSON.parse(request.getBody());
pwProgram.exitOverride();
await pwProgram.parseAsync(args, { from: 'user' });
return emptyWithLog('Installed browsers');
}

export async function grantPermissions(request: Request.Permissions, state: PlaywrightState): Promise<Response.Empty> {
const browserContext = state.getActiveContext();
if (!browserContext) {
Expand Down
3 changes: 2 additions & 1 deletion node/playwright-wrapper/grpc-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { ServerSurfaceCall } from '@grpc/grpc-js/build/src/server-call';
import { class_async_logger } from './keyword-decorators';
import { emptyWithLog, errorResponse, stringResponse } from './response-util';
import { pino } from 'pino';
const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime });
const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime, level: process.env.PINO_LOG_LEVEL || 'info'});

@class_async_logger
export class PlaywrightServer implements IPlaywrightServer {
Expand Down Expand Up @@ -360,6 +360,7 @@ export class PlaywrightServer implements IPlaywrightServer {
}

selectOption = this.wrapping(interaction.selectOption);
executePlaywright = this.wrapping(browserControl.executePlaywright);
grantPermissions = this.wrapping(browserControl.grantPermissions);
clearPermissions = this.wrapping(browserControl.clearPermissions);
deselectOption = this.wrapping(interaction.deSelectOption);
Expand Down
3 changes: 1 addition & 2 deletions node/playwright-wrapper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Server, ServerCredentials, ServiceDefinition, UntypedServiceImplementat

import { PlaywrightService } from './generated/playwright_grpc_pb';
import { pino } from 'pino';
const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime });
const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime, level: process.env.PINO_LOG_LEVEL || 'info' });

const args = process.argv.slice(2);

Expand All @@ -40,5 +40,4 @@ server.addService(

server.bindAsync(`${host}:${port}`, ServerCredentials.createInsecure(), () => {
logger.info(`Listening on ${host}:${port}`);
server.start();
});
1 change: 1 addition & 0 deletions protobuf/playwright.proto
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ service Playwright {
rpc SaveStorageState(Request.FilePath) returns (Response.Empty);

rpc GrantPermissions(Request.Permissions) returns (Response.Empty);
rpc ExecutePlaywright(Request.Json) returns (Response.Empty);
rpc ClearPermissions(Request.Empty) returns (Response.Empty);

/* Extension handling */
Expand Down
Loading