Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create entry and print version #1901

Merged
merged 11 commits into from
May 24, 2024
Merged
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
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ exclude = '''
profile = "black"
src_paths="."
skip_glob = ["src/SeleniumLibrary/__init__.pyi"]


[tool.pytest.ini_options]
pythonpath = [
"src"
]
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
selenium >= 4.3.0
robotframework >= 4.1.3
robotframework-pythonlibcore >= 3.0.0
robotframework-pythonlibcore >= 4.4.1
click >= 8.1.7
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
install_requires = REQUIREMENTS,
package_dir = {'': 'src'},
packages = find_packages('src'),
entry_points = {"console_scripts": ["selib=SeleniumLibrary.entry.__main__:cli"]},
package_data ={
'SeleniumLibrary':
['*.pyi']
Expand Down
71 changes: 69 additions & 2 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
# limitations under the License.
from collections import namedtuple
from datetime import timedelta
import importlib
from inspect import getdoc, isclass
from typing import Optional, List
from pathlib import Path
import pkgutil
from typing import Optional, List, Union

from robot.api import logger
from robot.errors import DataError
Expand Down Expand Up @@ -544,6 +547,46 @@ class SeleniumLibrary(DynamicCore):
documentation for further details.

Plugin API is new SeleniumLibrary 4.0

= Language =

SeleniumLibrary offers possibility to translte keyword names and documentation to new language. If language
is defined, SeleniumLibrary will search from
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#module-search-path | module search path]
Python packages starting with `robotframework_seleniumlibrary_translation` by using
[https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/ | Python pluging API]. Library
is using naming convention to find Python plugins.

The package must implement single API call, ``get_language`` without any arguments. Method must return a
dictionary containing two keys: ``language`` and ``path``. The language key value defines which language
the package contains. Also value should match (case insentive) the library ``language`` import parameter.
The path parameter value should be full path to the translation file.

== Translation file ==

The file name or extension is not important, but data must be in [https://www.json.org/json-en.html | json]
format. The keys of json are the methods names, not the keyword names, which implements keywords. Value of
key is json object which contains two keys: ``name`` and ``doc``. The ``name`` key contains the keyword
translated name and `doc` contains translated documentation. Providing doc and name are optional, example
translation json file can only provide translations to keyword names or only to documentatin. But it is
always recomended to provide translation to both name and doc. Special key ``__intro__`` is for class level
documentation and ``__init__`` is for init level documentation. These special values ``name`` can not be
translated, instead ``name`` should be kept the same.

== Generating template translation file ==

Template translation file, with English language can be created by running:
`rfselib translation /path/to/translation.json` command. Command does not provide translations to other
languages, it only provides easy way to create full list keywords and their documentation in correct
format. It is also possible to add keywords from library plugins by providing `--plugings` arguments
to command. Example: `rfselib translation --plugings myplugin.SomePlugin /path/to/translation.json` The
genered json file contains `sha256` key, which constains the sha256 sum of the library documentation,
the sha256 sum is used by `rfselib translation --compare /path/to/translation.json` command, which compares
transation to to library and prints outs a table which tell if there are changes needed for translation file.

Example project for translation can be found from
[https://github.com/MarketSquare/robotframework-seleniumlibrary-translation-fi | robotframework-seleniumlibrary-translation-fi]
repository.
"""

ROBOT_LIBRARY_SCOPE = "GLOBAL"
Expand All @@ -559,6 +602,7 @@ def __init__(
event_firing_webdriver: Optional[str] = None,
page_load_timeout=timedelta(minutes=5),
action_chain_delay=timedelta(seconds=0.25),
language: Optional[str] = None,
):
"""SeleniumLibrary can be imported with several optional arguments.

Expand All @@ -581,6 +625,8 @@ def __init__(
Default value to wait for page load to complete until a timeout exception is raised.
- ``action_chain_delay``:
Default value for `ActionChains` delay to wait in between actions.
- ``language``:
Defines language which is used to translate keyword names and documentation.
"""
self.timeout = _convert_timeout(timeout)
self.implicit_wait = _convert_timeout(implicit_wait)
Expand Down Expand Up @@ -622,7 +668,8 @@ def __init__(
self._plugins = plugin_libs
libraries = libraries + plugin_libs
self._drivers = WebDriverCache()
DynamicCore.__init__(self, libraries)
translation_file = self._get_translation(language)
DynamicCore.__init__(self, libraries, translation_file)

def run_keyword(self, name: str, args: tuple, kwargs: dict):
try:
Expand Down Expand Up @@ -798,3 +845,23 @@ def _resolve_screenshot_root_directory(self):
if is_string(screenshot_root_directory):
if screenshot_root_directory.upper() == EMBED:
self.screenshot_root_directory = EMBED

@staticmethod
def _get_translation(language: Union[str, None]) -> Union[Path, None]:
if not language:
return None
discovered_plugins = {
name: importlib.import_module(name)
for _, name, _ in pkgutil.iter_modules()
if name.startswith("robotframework_seleniumlibrary_translation")
}
for plugin in discovered_plugins.values():
try:
data = plugin.get_language()
except AttributeError:
continue
if data.get("language", "").lower() == language.lower() and data.get(
"path"
):
return Path(data.get("path")).absolute()
return None
15 changes: 15 additions & 0 deletions src/SeleniumLibrary/entry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2008-2011 Nokia Networks
# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
105 changes: 105 additions & 0 deletions src/SeleniumLibrary/entry/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2008-2011 Nokia Networks
# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from pathlib import Path
from typing import Optional
import click

from .get_versions import get_version
from .translation import compare_translatoin, get_library_translaton


CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
VERSION = get_version()


@click.group()
@click.version_option(VERSION)
def cli():
"""Robot Framework SeleniumLibrary command line tool.

Possible commands are:
translation


translation will generate detaul tranlsation json file from library keywords.

See each command argument help for more details what (optional) arguments that command supports.
"""
pass


@cli.command()
@click.argument(
"filename",
type=click.Path(exists=False, dir_okay=False, path_type=Path),
required=True,
)
@click.option(
"--plugings",
help="Same as plugins argument in the library import.",
default=None,
type=str,
)
@click.option(
"--compare",
help="Compares the translation file sha256 sum to library documentation.",
default=False,
is_flag=True,
show_default=True,
)
def translation(
filename: Path,
plugings: Optional[str] = None,
compare: bool = False,
):
"""Default translation file from library keywords.

This will help users to create their own translation as Python plugins. Command
will populate json file with english language. To create proper translation
file, users needs to translate the keyword name and doc arguments values in
json file.

The filename argument will tell where the default json file is saved.

The --pluging argument is same as plugins argument in the library import.
If you use plugins, it is also get default translation json file also witht
the plugin keyword included in the library.

If the --compare flag is set, then command does not generate template
translation file. Then it compares sha256 sums from the filenane
to ones read from the library documenentation. It will print out a list
of keywords which documentation sha256 does not match. This will ease
translation projects to identify keywords which documentation needs updating.
"""
translation = get_library_translaton(plugings)
if compare:
if table := compare_translatoin(filename, translation):
print(
"Found differences between translation and library, see below for details."
)
for line in table:
print(line)
else:
print("Translation is valid, no updated needed.")
else:
with filename.open("w") as file:
json.dump(translation, file, indent=4)
print(f"Translation file created in {filename.absolute()}")


if __name__ == "__main__":
cli()
51 changes: 51 additions & 0 deletions src/SeleniumLibrary/entry/get_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2008-2011 Nokia Networks
# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from pathlib import Path
import re
import subprocess
import sys

from selenium import __version__

INSTALL_DIR = Path(__file__).parent.parent


def get_rf_version() -> str:
process = subprocess.run(
[sys.executable, "-m", "robot", "--version"], capture_output=True, check=False
)
return process.stdout.decode("utf-8").split(" ")[2]


def get_library_version() -> str:
init_file = INSTALL_DIR / "__init__.py"
with init_file.open("r") as file:
data = file.read()
return re.search('\n__version__ = "(.*)"', data).group(1)


def get_version():
"""Display Python, Robot Framework, SeleniumLibrary and selenium versions"""
python_version = (
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
)
return (
f"\nUsed Python is: {sys.executable}\n\tVersion: {python_version}\n"
f'Robot Framework version: "{get_rf_version()}\n"'
f"Installed SeleniumLibrary version is: {get_library_version()}\n"
f"Installed selenium version is: {__version__}\n"
)
Loading
Loading