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

Translation documentation #1907

Merged
merged 6 commits into from
Jun 10, 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
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
matrix:
python-version: [3.8, 3.11] # 3.12, pypy-3.9
rf-version: [5.0.1, 6.1.1, 7.0]
selenium-version: [4.16.0, 4.17.2, 4.18.1, 4.19.0, 4.20.0, 4.21.0]
selenium-version: [4.20.0, 4.21.0]
browser: [firefox, chrome, headlesschrome] #edge

steps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ ${event_firing_or_none} ${NONE}
Open Browser To Start Page
[Tags] NoGrid
[Documentation]
... LOG 1:31 DEBUG Wrapping driver to event_firing_webdriver.
... LOG 1:33 INFO Got driver also from SeleniumLibrary.
... LOG 1:30 DEBUG Wrapping driver to event_firing_webdriver.
... LOG 1:32 INFO Got driver also from SeleniumLibrary.
Open Browser ${FRONT_PAGE} ${BROWSER} remote_url=${REMOTE_URL}

Event Firing Webdriver Go To (WebDriver)
Expand Down
2 changes: 1 addition & 1 deletion atest/acceptance/create_webdriver.robot
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Library Collections
Create Webdriver Creates Functioning WebDriver
[Documentation]
... LOG 1:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver.
... LOG 1:19 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+.
... LOG 1:18 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+.
[Tags] Known Issue Internet Explorer Known Issue Safari
[Setup] Set Driver Variables
Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS}
Expand Down
4 changes: 2 additions & 2 deletions atest/acceptance/keywords/page_load_timeout.robot
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Test Teardown Close Browser And Reset Page Load Timeout
*** Test Cases ***
Should Open Browser With Default Page Load Timeout
[Documentation] Verify that 'Open Browser' changes the page load timeout.
... LOG 1.1.1:27 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {['\\\"]pageLoad['\\\"]: 300000}
... LOG 1.1.1:29 DEBUG STARTS: Remote response: status=200
... LOG 1.1.1:26 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {['\\\"]pageLoad['\\\"]: 300000}
... LOG 1.1.1:28 DEBUG STARTS: Remote response: status=200
# Note: previous log check was 33 and 37. Recording to see if something is swtiching back and forth
Open Browser To Start Page

Expand Down
24 changes: 12 additions & 12 deletions atest/acceptance/multiple_browsers_options.robot
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ Documentation Creating test which would work on all browser is not possible.
*** Test Cases ***
Chrome Browser With Selenium Options As String
[Documentation]
... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage")

Chrome Browser With Selenium Options As String With Attribute As True
[Documentation]
... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*['\\\"]--headless=new['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]--headless=new['\\\"].*
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument ( "--disable-dev-shm-usage" ) ; add_argument ( "--headless=new" )

Chrome Browser With Selenium Options With Complex Object
[Tags] NoGrid
[Documentation]
... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*['\\\"]mobileEmulation['\\\"]: {['\\\"]deviceName['\\\"]: ['\\\"]Galaxy S5['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]mobileEmulation['\\\"]: {['\\\"]deviceName['\\\"]: ['\\\"]Galaxy S5['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument ( "--disable-dev-shm-usage" ) ; add_experimental_option( "mobileEmulation" , { 'deviceName' : 'Galaxy S5'})

Chrome Browser With Selenium Options Object
[Documentation]
... LOG 2:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 2:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
... LOG 2:13 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 2:13 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].*
${options} = Get Chrome Options
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
... desired_capabilities=${DESIRED_CAPABILITIES} options=${options}
Expand All @@ -47,8 +47,8 @@ Chrome Browser With Selenium Options Invalid Method

Chrome Browser With Selenium Options Argument With Semicolon
[Documentation]
... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:14 DEBUG REGEXP: .*\\\[['\\\"]has;semicolon['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].*
... LOG 1:13 DEBUG REGEXP: .*\\\[['\\\"]has;semicolon['\\\"].*
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon")

Expand Down
8 changes: 4 additions & 4 deletions atest/acceptance/multiple_browsers_service.robot
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Documentation Creating test which would work on all browser is not possible.
*** Test Cases ***
Chrome Browser With Chrome Service As String
[Documentation]
... LOG 2:2 DEBUG STARTS: Started executable:
... LOG 2:3 DEBUG GLOB: POST*/session*
... LOG 2:3 DEBUG STARTS: Started executable:
... LOG 2:4 DEBUG GLOB: POST*/session*
${driver_path}= Get Driver Path Chrome
Open Browser ${FRONT PAGE} Chrome remote_url=${REMOTE_URL}
... service=executable_path='${driver_path}'
Expand All @@ -24,8 +24,8 @@ Chrome Browser With Chrome Service As String With service_args As List

Firefox Browser With Firefox Service As String
[Documentation]
... LOG 2:2 DEBUG STARTS: Started executable:
... LOG 2:3 DEBUG GLOB: POST*/session*
... LOG 2:3 DEBUG STARTS: Started executable:
... LOG 2:4 DEBUG GLOB: POST*/session*
${driver_path}= Get Driver Path Firefox
Open Browser ${FRONT PAGE} Firefox remote_url=${REMOTE_URL}
... service=executable_path='${driver_path}'
Expand Down
4 changes: 2 additions & 2 deletions atest/acceptance/open_and_close.robot
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ Close Browser Does Nothing When No Browser Is Opened

Browser Open With Not Well-Formed URL Should Close
[Documentation] Verify after incomplete 'Open Browser' browser closes
... LOG 1.1:35 DEBUG STARTS: Opened browser with session id
... LOG 1.1:35 DEBUG REGEXP: .*but failed to open url.*
... LOG 1.1:34 DEBUG STARTS: Opened browser with session id
... LOG 1.1:34 DEBUG REGEXP: .*but failed to open url.*
... LOG 2:2 DEBUG STARTS: DELETE
... LOG 2:5 DEBUG STARTS: Finished Request
Run Keyword And Expect Error * Open Browser bad.url.bad ${BROWSER}
Expand Down
21 changes: 11 additions & 10 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,16 +550,16 @@ class SeleniumLibrary(DynamicCore):

= Language =

SeleniumLibrary offers possibility to translte keyword names and documentation to new language. If language
SeleniumLibrary offers the possibility to translate 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
for Python packages starting with `robotframework-seleniumlibrary-translation` by using the
[https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/ | Python pluging API]. The 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
The package must implement a single API call, ``get_language`` without any arguments. The 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 package contains. Also the value should match (case insensitive) the library ``language`` import parameter.
The path parameter value should be full path to the translation file.

== Translation file ==
Expand All @@ -568,8 +568,8 @@ class SeleniumLibrary(DynamicCore):
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
translation json file can only provide translations to keyword names or only to documentation. But it is
always recommended 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.

Expand All @@ -580,9 +580,10 @@ class SeleniumLibrary(DynamicCore):
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.
generated json file contains `sha256` key, which contains the sha256 sum of the library documentation.
The sha256 sum is used by `rfselib translation --compare /path/to/translation.json` command, which compares
the translation to the library and prints outs a table which tells if there are changes needed for
the translation file.

Example project for translation can be found from
[https://github.com/MarketSquare/robotframework-seleniumlibrary-translation-fi | robotframework-seleniumlibrary-translation-fi]
Expand Down
32 changes: 16 additions & 16 deletions src/SeleniumLibrary/entry/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
# 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
from .translation import compare_translation, get_library_translation


CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
Expand All @@ -32,10 +33,9 @@ def cli():
"""Robot Framework SeleniumLibrary command line tool.

Possible commands are:
translation


translation will generate detaul tranlsation json file from library keywords.
translation
will generate template translation json file from library keywords.

See each command argument help for more details what (optional) arguments that command supports.
"""
Expand All @@ -49,7 +49,7 @@ def cli():
required=True,
)
@click.option(
"--plugings",
"--plugin",
help="Same as plugins argument in the library import.",
default=None,
type=str,
Expand All @@ -63,31 +63,31 @@ def cli():
)
def translation(
filename: Path,
plugings: Optional[str] = None,
plugins: 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
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.
The --plugin argument will add plugin keywords in addition to the library keywords
into the default translation json file. It is used the same as plugins argument in
the library import.

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 file. Instead it compares sha256 sums from the filename
to the one read from the library documentation. It will print out a list
of keywords for which the documentation sha256 does not match. This will ease
translating projects to identify keywords which needs updating.
"""
translation = get_library_translaton(plugings)
lib_translation = get_library_translation(plugins)
if compare:
if table := compare_translatoin(filename, translation):
if table := compare_translation(filename, lib_translation):
print(
"Found differences between translation and library, see below for details."
)
Expand Down
28 changes: 14 additions & 14 deletions src/SeleniumLibrary/entry/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
DOC_CHANGED = "Documentation update needed"
NO_LIB_KEYWORD = "Keyword not found from library"
MISSING_TRANSLATION = "Keyword is missing translation"
MISSING_CHECKSUM = "Keyword tranlsaton is missing checksum"
MISSING_CHECKSUM = "Keyword translation is missing checksum"
MAX_REASON_LEN = max(
len(DOC_CHANGED),
len(NO_LIB_KEYWORD),
Expand All @@ -33,10 +33,10 @@
)


def get_library_translaton(plugings: Optional[str] = None) -> dict:
def get_library_translation(plugins: Optional[str] = None) -> dict:
from SeleniumLibrary import SeleniumLibrary

selib = SeleniumLibrary(plugins=plugings)
selib = SeleniumLibrary(plugins=plugins)
translation = {}
for function in selib.attributes.values():
translation[function.__name__] = {
Expand All @@ -57,18 +57,18 @@ def get_library_translaton(plugings: Optional[str] = None) -> dict:
return translation


def _max_kw_name_lenght(project_tanslation: dict) -> int:
def _max_kw_name_length(project_translation: dict) -> int:
max_lenght = 0
for keyword_data in project_tanslation.values():
for keyword_data in project_translation.values():
if (current_kw_length := len(keyword_data["name"])) > max_lenght:
max_lenght = current_kw_length
return max_lenght


def _get_heading(max_kw_lenght: int) -> List[str]:
def _get_heading(max_kw_length: int) -> List[str]:
heading = f"| {KEYWORD_NAME} "
next_line = f"| {'-' * len(KEYWORD_NAME)}"
if (padding := max_kw_lenght - len(KEYWORD_NAME)) > 0:
if (padding := max_kw_length - len(KEYWORD_NAME)) > 0:
heading = f"{heading}{' ' * padding}"
next_line = f"{next_line}{'-' * padding}"
reason = "Reason"
Expand All @@ -89,34 +89,34 @@ def _table_doc_updated(lib_kw: str, max_name_lenght: int, reason: str) -> str:
return f"{line}|"


def compare_translatoin(filename: Path, library_translation: dict):
def compare_translation(filename: Path, library_translation: dict):
with filename.open("r") as file:
project_translation = json.load(file)
max_kw_lenght = _max_kw_name_lenght(library_translation)
max_kw_length = _max_kw_name_length(library_translation)
table_body = []
for lib_kw, lib_kw_data in library_translation.items():
project_kw_data = project_translation.get(lib_kw)
if not project_kw_data:
table_body.append(
_table_doc_updated(lib_kw, max_kw_lenght, MISSING_TRANSLATION)
_table_doc_updated(lib_kw, max_kw_length, MISSING_TRANSLATION)
)
continue
sha256_value = project_kw_data.get("sha256")
if not sha256_value:
table_body.append(
_table_doc_updated(lib_kw, max_kw_lenght, MISSING_CHECKSUM)
_table_doc_updated(lib_kw, max_kw_length, MISSING_CHECKSUM)
)
continue
if project_kw_data["sha256"] != lib_kw_data["sha256"]:
table_body.append(_table_doc_updated(lib_kw, max_kw_lenght, DOC_CHANGED))
table_body.append(_table_doc_updated(lib_kw, max_kw_length, DOC_CHANGED))
for project_kw in project_translation:
if project_kw not in library_translation:
table_body.append(
_table_doc_updated(project_kw, max_kw_lenght, NO_LIB_KEYWORD)
_table_doc_updated(project_kw, max_kw_length, NO_LIB_KEYWORD)
)
if not table_body:
return []

table = _get_heading(max_kw_lenght)
table = _get_heading(max_kw_length)
table.extend(table_body)
return table
Loading