diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml index 6a3217c..9eb3e7a 100644 --- a/.github/workflows/generate-changelog.yml +++ b/.github/workflows/generate-changelog.yml @@ -1,17 +1,16 @@ name: Changelog CI -on: - pull_request: - types: [ opened ] +#on: +# pull_request: +# types: [ opened ] +# Not working for now... jobs: build: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - - name: Run Changelog CI - uses: saadmk11/changelog-ci@v1.1.2 + uses: saadmk11/changelog-ci@v1.2.0 with: - config_file: changelog-ci-config.yaml + config_file: changelog-ci-config.yaml \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5f42fc7..ae60e3b 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,7 +1,11 @@ # This workflow runs the test suites with different versions of python name: tests -on: [push, pull_request] +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: tests: @@ -14,74 +18,74 @@ jobs: # pub400 closes the connection if there are too many requests coming from the same IP address max-parallel: 1 matrix: - python-version: ["3.8", "3.12", "3.13"] + python-version: ["3.12"] os: [ubuntu-latest, windows-latest] fail-fast: false steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies linux - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get install -y xvfb x3270 locales xterm - sudo locale-gen en_US - - - name: Install dependencies windows - if: matrix.os == 'windows-latest' - run: | - choco install wc3270 - echo "C:\Program Files\wc3270" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install python dependencies - run: | - python -m pip install -r requirements-dev.txt - - - name: Lint - run: | - inv lint - - - name: Run utests with coverage - run: | - coverage run --branch --source Mainframe3270/ -m pytest --verbose utest/ - coverage report - coverage xml - - - name: Upload unit test coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: coverage.xml - flags: unit - move_coverage_to_trash: true - - - name: Run atests with coverage linux - if: matrix.os == 'ubuntu-latest' - run: | - LANG=en_US.iso88591 xvfb-run coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ - coverage report - coverage xml - - - name: Run atests with coverage windows - if: matrix.os == 'windows-latest' - run: | - coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ - coverage report - coverage xml - - - name: Upload acceptance tests coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: coverage.xml - flags: acceptance - - - uses: actions/upload-artifact@v4 - if: ${{ always() }} - with: - name: Tests results python${{ matrix.python-version }} - ${{ matrix.os }} - path: logs/ + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies linux + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get install -y xvfb x3270 locales xterm + sudo locale-gen en_US + + - name: Install dependencies windows + if: matrix.os == 'windows-latest' + run: | + choco install wc3270 + echo "C:\Program Files\wc3270" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Install python dependencies + run: | + python -m pip install -r requirements-dev.txt + + - name: Lint + run: | + inv lint + + - name: Run utests with coverage + run: | + coverage run --branch --source Mainframe3270/ -m pytest --verbose utest/ + coverage report + coverage xml + + - name: Upload unit test coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage.xml + flags: unit + move_coverage_to_trash: true + + - name: Run atests with coverage linux + if: matrix.os == 'ubuntu-latest' + run: | + LANG=en_US.iso88591 xvfb-run coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ + coverage report + coverage xml + + - name: Run atests with coverage windows + if: matrix.os == 'windows-latest' + run: | + coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ + coverage report + coverage xml + + - name: Upload acceptance tests coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage.xml + flags: acceptance + + - uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: Tests results python${{ matrix.python-version }} - ${{ matrix.os }} + path: logs/ diff --git a/.gitignore b/.gitignore index 23681f5..1a97f27 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ log.html output.xml report.html screenshot*.html +screenshot*.png atest/**/session.*3270 atest/log.html atest/output.xml @@ -36,3 +37,7 @@ htmlcov # Personal test cases atest/test_with_login.robot +utest/ +.hypothesis/ +.robotcode_cache/ +*.isorted diff --git a/Mainframe3270/__init__.py b/Mainframe3270/__init__.py index 055460c..37e6cc6 100644 --- a/Mainframe3270/__init__.py +++ b/Mainframe3270/__init__.py @@ -1,13 +1,11 @@ import os from datetime import timedelta from typing import Any - from robot.api import logger from robot.api.deco import keyword from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError from robot.utils import ConnectionCache from robotlibcore import DynamicCore - from Mainframe3270.keywords import ( AssertionKeywords, CommandKeywords, @@ -150,17 +148,16 @@ def __init__( self.timeout = convert_timeout(timeout) self.wait_time = convert_timeout(wait_time) self.wait_time_after_write = convert_timeout(wait_time_after_write) - self.img_folder = img_folder - self._running_on_failure_keyword = False - self.register_run_on_failure_keyword(run_on_failure_keyword) - self.model = model - self.cache = ConnectionCache() # When generating the library documentation with libdoc, BuiltIn.get_variable_value throws # a RobotNotRunningError. Therefore, we catch it here to be able to generate the documentation. try: - self.output_folder = BuiltIn().get_variable_value("${OUTPUT DIR}") + self.img_folder = BuiltIn().get_variable_value("${OUTPUT_DIR}") except RobotNotRunningError: - self.output_folder = os.getcwd() + self.img_folder = os.getcwd() + self._running_on_failure_keyword = False + self.register_run_on_failure_keyword(run_on_failure_keyword) + self.model = model + self.cache = ConnectionCache() libraries = [ AssertionKeywords(self), CommandKeywords(self), diff --git a/Mainframe3270/keywords/assertions.py b/Mainframe3270/keywords/assertions.py index fda1937..28157ec 100644 --- a/Mainframe3270/keywords/assertions.py +++ b/Mainframe3270/keywords/assertions.py @@ -1,10 +1,8 @@ import re from typing import List, Optional - from robot.api import logger from robot.api.deco import keyword from robot.utils import Matcher - from Mainframe3270.librarycomponent import LibraryComponent diff --git a/Mainframe3270/keywords/commands.py b/Mainframe3270/keywords/commands.py index 58ae67e..0d43650 100644 --- a/Mainframe3270/keywords/commands.py +++ b/Mainframe3270/keywords/commands.py @@ -1,8 +1,6 @@ import time from typing import Optional, Union - from robot.api.deco import keyword - from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.utils import ResultMode, prepare_position_as diff --git a/Mainframe3270/keywords/connection.py b/Mainframe3270/keywords/connection.py index f15ab92..dfa993f 100644 --- a/Mainframe3270/keywords/connection.py +++ b/Mainframe3270/keywords/connection.py @@ -3,10 +3,8 @@ import shlex from os import name as os_name from typing import List, Optional, Union - from robot.api import logger from robot.api.deco import keyword - from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.py3270 import Emulator @@ -16,10 +14,11 @@ class ConnectionKeywords(LibraryComponent): def open_connection( self, host: str, - LU: Optional[str] = None, + lu: Optional[str] = None, port: int = 23, extra_args: Optional[Union[List[str], os.PathLike]] = None, alias: Optional[str] = None, + utf8: bool = True, ) -> int: """Create a connection to an IBM3270 mainframe with the default port 23. To establish a connection, only the hostname is required. @@ -41,6 +40,9 @@ def open_connection( | -charset french | -port 992 + By default, the `utf8` argument is set to `True`, which will add the `-utf8` option to the `extra_args` list. + If you want to disable UTF-8 encoding, you can set the `utf8` argument to `False`. + Please ensure that the arguments provided are available for your specific x3270 application and version. Refer to the [https://x3270.miraheze.org/wiki/Wc3270/Command-line_options|wc3270 command line options] for a subset of available options. @@ -53,20 +55,26 @@ def open_connection( when switching between connections using the `Switch Connection` keyword. For more information on opening and switching between multiple connections, please refer to the `Concurrent Connections` section. - Example: + Examples: | Open Connection | Hostname | | Open Connection | Hostname | LU=LUname | | Open Connection | Hostname | port=992 | + | Open Connection | Hostname | utf8=${False} | | @{extra_args} | Create List | -accepthostname | myhost.com | -cafile | ${CURDIR}/cafile.crt | | Append To List | ${extra_args} | -port | 992 | | Open Connection | Hostname | extra_args=${extra_args} | | Open Connection | Hostname | extra_args=${CURDIR}/argfile.txt | | Open Connection | Hostname | alias=my_first_connection | """ + if utf8: + if extra_args is None: + extra_args = ["-utf8"] + elif isinstance(extra_args, list): + extra_args.append("-utf8") extra_args = self._process_args(extra_args) model = self._get_model_from_list_or_file(extra_args) connection = Emulator(self.visible, self.timeout, extra_args, model or self.model) - host_string = f"{LU}@{host}" if LU else host + host_string = f"{lu}@{host}" if lu else host if self._port_in_extra_args(extra_args): if port != 23: logger.warn( diff --git a/Mainframe3270/keywords/read_write.py b/Mainframe3270/keywords/read_write.py index 5d8430c..8128ffb 100644 --- a/Mainframe3270/keywords/read_write.py +++ b/Mainframe3270/keywords/read_write.py @@ -1,9 +1,6 @@ import time from typing import Any, Optional - from robot.api.deco import keyword -from robot.libraries.BuiltIn import BuiltIn - from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.utils import ResultMode, prepare_positions_as @@ -124,12 +121,13 @@ def write(self, txt: str) -> None: @keyword("Write Unicode") def write_unicode(self, txt: str) -> None: - """Send a string *and Enter* to the screen at the current cursor location. + """Send a Unicode string using the execute command String("") *and Enter* to the screen at the current + cursor location. Example: - | Write Unicode | something | + | Write Unicode | ß | """ - self.mf.exec_command(f'String("{txt}")'.encode('utf-8')) + self.mf.exec_command(f'String("{txt}")'.encode("utf-8")) time.sleep(self.wait_time_after_write) self.mf.send_enter() @@ -144,12 +142,12 @@ def write_bare(self, txt: str) -> None: @keyword("Write Unicode Bare") def write_unicode_bare(self, txt: str) -> None: - """Send only the string to the screen at the current cursor location. + """Send only the Unicode string using the execute command String("") to the screen at the current cursor location. Example: - | Write Unicode Bare | something | + | Write Unicode Bare | Æ | """ - self.mf.exec_command(f'String("{txt}")'.encode('utf-8')) + self.mf.exec_command(f'String("{txt}")'.encode("utf-8")) time.sleep(self.wait_time_after_write) @keyword("Write In Position") diff --git a/Mainframe3270/keywords/screenshot.py b/Mainframe3270/keywords/screenshot.py index 295a68f..88eef49 100644 --- a/Mainframe3270/keywords/screenshot.py +++ b/Mainframe3270/keywords/screenshot.py @@ -1,11 +1,17 @@ import os import time - from robot.api import logger from robot.api.deco import keyword - from Mainframe3270.librarycomponent import LibraryComponent +try: + from html2image import Html2Image + + hti = Html2Image(size=(600, 500)) +except Exception as exception: + logger.info("\n" + str(exception), also_console=True) + logger.info("Chrome not found. Take Screenshot will work only in html format.", also_console=True) + class ScreenshotKeywords(LibraryComponent): @keyword("Set Screenshot Folder") @@ -18,37 +24,54 @@ def set_screenshot_folder(self, path: str) -> None: Example: | Set Screenshot Folder | C:\\Temp\\Images | """ - if os.path.exists(os.path.normpath(os.path.join(self.output_folder, path))): + if os.path.exists(os.path.normpath(os.path.join(self.img_folder, path))): self.img_folder = path else: logger.error(f'Given screenshots path "{path}" does not exist') logger.warn(f'Screenshots will be saved in "{self.img_folder}"') @keyword("Take Screenshot") - def take_screenshot(self, height: int = 410, width: int = 670, filename_prefix: str = "screenshot") -> str: - """Generate a screenshot of the IBM 3270 Mainframe in a html format. The + def take_screenshot( + self, height: int = 400, width: int = 600, filename_prefix: str = "screenshot", img: bool = False + ) -> str: + """Generate a screenshot of the IBM 3270 Mainframe in a html or png format. The default folder is the log folder of RobotFramework, if you want change see the `Set Screenshot Folder`. - The Screenshot is printed in an iframe log, with the values of height=410 and width=670, you + The Screenshot is printed in an iframe log, with the values of height=400 and width=600, you can change these values by passing them to the keyword. + Default format is html, but you can change it to png by passing img=${True} to the keyword. Because the html2image + module this option only works if chrome is installed in your system. + The file name prefix can be set, the default is "screenshot". - The full file path is returned. + The file path is returned. Example: | ${filepath} | Take Screenshot | + | ${filepath} | Take Screenshot | img=${True} | | ${filepath} | Take Screenshot | height=500 | width=700 | | Take Screenshot | height=500 | width=700 | | Take Screenshot | filename_prefix=MyScreenshot | """ - extension = "html" - filename_sufix = round(time.time() * 1000) - filepath = os.path.join(self.img_folder, "%s_%s.%s" % (filename_prefix, filename_sufix, extension)) - self.mf.save_screen(os.path.join(self.output_folder, filepath)) - logger.write( - '' % (filepath.replace("\\", "/"), height, width), - level="INFO", - html=True, - ) + filename_suffix = str(round(time.time() * 1000)) + filepath = os.path.join(self.img_folder, f"{filename_prefix}_{filename_suffix}.html") + self.mf.save_screen(filepath) + if img: + try: + hti.output_path = self.img_folder + except Exception as exception: + logger.info("\n" + str(exception), also_console=True) + raise EnvironmentError("Chrome not found, please use argument ${img}=False") + img_name = f"{filename_prefix}_{filename_suffix}.png" + img_path = os.path.join(self.img_folder, img_name) + hti.screenshot(html_file=filepath, save_as=img_name) + logger.info(f"", html=True) + return img_path + else: + logger.write( + '' % (filepath.replace("\\", "/"), height, width), + level="INFO", + html=True, + ) return filepath diff --git a/Mainframe3270/keywords/wait_and_timeout.py b/Mainframe3270/keywords/wait_and_timeout.py index f42eb60..1c2986c 100644 --- a/Mainframe3270/keywords/wait_and_timeout.py +++ b/Mainframe3270/keywords/wait_and_timeout.py @@ -1,9 +1,7 @@ import time from datetime import timedelta - from robot.api.deco import keyword from robot.utils import secs_to_timestr - from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.utils import convert_timeout diff --git a/Mainframe3270/librarycomponent.py b/Mainframe3270/librarycomponent.py index c714f6f..9819c3e 100644 --- a/Mainframe3270/librarycomponent.py +++ b/Mainframe3270/librarycomponent.py @@ -1,5 +1,4 @@ from robot.utils import ConnectionCache - from Mainframe3270.py3270 import Emulator diff --git a/Mainframe3270/py3270.py b/Mainframe3270/py3270.py index c7baaf7..a129ab3 100644 --- a/Mainframe3270/py3270.py +++ b/Mainframe3270/py3270.py @@ -10,7 +10,6 @@ from abc import ABC, abstractmethod from contextlib import closing from os import name as os_name - from robot.utils import seq2str log = logging.getLogger(__name__) @@ -476,7 +475,7 @@ def string_get(self, ypos, xpos, length): cmd = self.exec_command("ascii({0},{1},{2})".format(ypos, xpos, length).encode("utf-8")) # this usage of utf-8 should only return a single line of data assert len(cmd.data) == 1, cmd.data - return cmd.data[0].decode("utf-8") + return cmd.data[0].decode("utf-8", errors="replace") def search_string(self, string, ignore_case=False): """ @@ -508,10 +507,12 @@ def read_all_screen(self): for ypos in range(self.model_dimensions["rows"]): full_text += self.string_get(ypos + 1, 1, self.model_dimensions["columns"]) - # The following section is necessary for cross platform compatibility if the host application contains some special characters. - # It replaces the unicode values with the corresponding characters to prevent positioning errors which are caused by the different client implementations (wc3270 vs. x3270) + # The following section is necessary for cross-platform compatibility if the host application contains some + # special characters. It replaces the Unicode values with the corresponding characters to prevent positioning + # errors which are caused by the different client implementations (wc3270 vs. x3270) - # Replace various box drawing character patterns (multiple stages for different character types, I added them one by one to catch all cases in out tests) + # Replace various box drawing character patterns (multiple stages for different character types, + # I added them one by one to catch all cases in out tests) # Stage 1: Replace specific multi-byte sequences full_text = full_text.replace("â\x94\x80", "-") # Horizontal line diff --git a/Mainframe3270/utils.py b/Mainframe3270/utils.py index a4e7005..d545f5a 100644 --- a/Mainframe3270/utils.py +++ b/Mainframe3270/utils.py @@ -1,7 +1,6 @@ from datetime import timedelta from enum import Enum, auto from typing import List, Tuple - from robot.api import logger from robot.utils import timestr_to_secs diff --git a/Mainframe3270/version.py b/Mainframe3270/version.py index fe8be31..682eafd 100644 --- a/Mainframe3270/version.py +++ b/Mainframe3270/version.py @@ -1 +1 @@ -VERSION = "4.1" +VERSION = "4.2" diff --git a/atest/HelperLibrary.py b/atest/HelperLibrary.py index ebca38d..f2cafaf 100644 --- a/atest/HelperLibrary.py +++ b/atest/HelperLibrary.py @@ -1,6 +1,5 @@ import os import time - from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError diff --git a/atest/connection.robot b/atest/connection.robot index f3e4a84..a1ffca0 100644 --- a/atest/connection.robot +++ b/atest/connection.robot @@ -30,10 +30,10 @@ Test Connection From Session File Page Should Contain String ${WELCOME} Test Concurrent Connections - Open Connection ${HOST} alias=first extra_args=["-utf8"] + Open Connection ${HOST} alias=first Write Bare ABCD Page Should Contain String ABCD - Open Connection ${HOST} alias=second extra_args=["-utf8"] + Open Connection ${HOST} alias=second Write Bare DEFG Page Should Contain String DEFG Page Should Not Contain String ABCD diff --git a/atest/mainframe.robot b/atest/mainframe.robot index bd5315f..392387d 100644 --- a/atest/mainframe.robot +++ b/atest/mainframe.robot @@ -201,6 +201,13 @@ Test Read All Screen Should Contain ${screen_content} c I Should Not Contain ${screen_content} xyz +Test Take Screenshot + [Tags] no-ci + ${html_file} Take Screenshot + File Should Exist ${html_file} + ${png_file} Take Screenshot img=${True} + File Should Exist ${png_file} + Test Write Bare Move Cursor To 5 25 Write Bare ${WRITE_TEXT} @@ -223,6 +230,14 @@ Test Delete Char Should Be Equal As Strings ${TEXT_AFTER_DELETE_CHAR} ${read_text} Sleep 1s +Test Write Unicode Bare + Move Cursor To 5 25 + Write Unicode Bare ${WRITE_TEXT_UNICODE} + ${read_text} Read 5 25 6 + Take Screenshot + Should Be Equal As Strings ${WRITE_TEXT_UNICODE} ${read_text} + Sleep 1s + Test Delete Field Delete Field ${read_text} Read 5 25 8 @@ -271,17 +286,16 @@ Test Get Current Position Should Be Equal ${{ {"xpos": 27, "ypos": 6} }} ${position_as_dict} Test Get String Positions - ${positions} Get String Positions Welcome - Should Be Equal ${{ [(1, 10), (9, 15)] }} ${positions} + ${positions} Get String Positions Subsystem + Should Be Equal ${{ [(3, 48)] }} ${positions} Test Get String Positions Case-Insensitive - ${positions} Get String Positions Welcome ignore_case=True - Should Be Equal ${{ [(1, 10), (9, 15)] }} ${positions} + ${positions} Get String Positions subsystem ignore_case=True + Should Be Equal ${{ [(3, 48)] }} ${positions} Test Get String Positions As Dict - ${positions} Get String Positions Welcome As Dict - Should Be Equal ${{ [{"ypos": 1, "xpos": 10}, {"ypos": 9, "xpos": 15}] }} - ... ${positions} + ${positions} Get String Positions Subsystem As Dict + Should Be Equal ${{ [{"ypos": 3, "xpos": 48}] }} ${positions} Test Get String Positions Without Result ${positions} Get String Positions ${STRING_NON_EXISTENT} @@ -298,11 +312,11 @@ Test Get String Positions Only After As Dict Should Be Equal ${{ [{'ypos': 5, 'xpos': 11}, {'ypos': 21, 'xpos': 38}] }} ${positions} Test Get String Positions Only After Case-Insensitive - ${positions} Get String Positions Only After 9 4 Welcome ignore_case=True + ${positions} Get String Positions Only After 9 4 welcome ignore_case=True Should Be Equal ${{ [(9, 15)] }} ${positions} Test Get String Positions Only After Without Results - ${positions} Get String Positions Only After 9 15 Welcome ignore_case=True + ${positions} Get String Positions Only After 10 5 Welcome ignore_case=True Should Be Empty ${positions} Test Get String Positions Only Before @@ -324,7 +338,7 @@ Test Get String Positions Only Before Without Results *** Keywords *** Suite Mainframe Setup - Open Connection ${HOST} extra_args=["-utf8"] + Open Connection ${HOST} Create Directory ${FOLDER} Empty Directory ${FOLDER} Set Screenshot Folder ${FOLDER} diff --git a/atest/pub400_variables.robot b/atest/pub400_variables.robot index 1e08469..8710f40 100644 --- a/atest/pub400_variables.robot +++ b/atest/pub400_variables.robot @@ -5,6 +5,7 @@ ${FOLDER} ${CURDIR}${/}screenshots # Text to write ${WRITE_TEXT} TEST ${WRITE_TEXT_UTF8} _ëçá +${WRITE_TEXT_UNICODE} ÆæßÆæß # Texts in the Mainframe ${WELCOME} Welcome to PUB400.COM ${WELCOME_TITLE} Welcome to PUB400.COM * your public IBM i server diff --git a/atest/run_on_failure/run_on_failure.robot b/atest/run_on_failure/run_on_failure.robot index 381bc0a..95da94e 100644 --- a/atest/run_on_failure/run_on_failure.robot +++ b/atest/run_on_failure/run_on_failure.robot @@ -13,6 +13,7 @@ ${CUSTOM_FILE} ${CURDIR}${/}output.txt *** Test Cases *** Takes Screenshot On Failure + [Tags] no-ci Cause Error File Should Exist ${CURDIR}/*.html [Teardown] Remove File ${CURDIR}/*.html diff --git a/doc/Mainframe3270.html b/doc/Mainframe3270.html index d2621d1..8bbaaa1 100644 --- a/doc/Mainframe3270.html +++ b/doc/Mainframe3270.html @@ -1,1855 +1,410 @@ - + - - - - - - - - - - - - - + + + + + + + - - - - - - - - - -
-

Opening library documentation failed

- -
- - - - - // http://stackoverflow.com/a/18484799 - var delay = (function () { - var timer = 0; - return function(callback, ms) { - clearTimeout(timer); - timer = setTimeout(callback, ms); - }; - })(); - + + + + - - - + + + + + + - - - - - - - + + + + - - + + - - - - - - - - - - - - + {{#if usages.length}} +
+

{{t "usages"}}

+ +
+ {{/if}} + + + + + + diff --git a/doc/Mainframe3270.xml b/doc/Mainframe3270.xml index e8f8272..bbff677 100644 --- a/doc/Mainframe3270.xml +++ b/doc/Mainframe3270.xml @@ -1,6 +1,6 @@ - -4.1 + +4.2 Mainframe3270 is a library for Robot Framework based on the [https://pypi.org/project/py3270/|py3270 project], a Python interface to x3270, an IBM 3270 terminal emulator. It provides an API to a x3270 or s3270 subprocess. @@ -98,37 +98,37 @@ Note that this is an experimental feature, so not all models might work as expec visible -bool + True timeout -timedelta + 0:00:30 wait_time -timedelta + 0:00:00.500000 wait_time_after_write -timedelta + 0:00:00 img_folder -str + . run_on_failure_keyword -str + Take Screenshot model -str + 2 @@ -152,11 +152,11 @@ To change the ``run_on_failure_keyword`` during runtime, see `Register Run On Fa - + seconds -timedelta + Change the timeout for connection in seconds. @@ -165,11 +165,11 @@ Example: | Change Timeout | 3 seconds | Change the timeout for connection in seconds. - + wait_time -timedelta + To give time for the mainframe screen to be "drawn" and receive the next commands, a "wait time" has been @@ -189,11 +189,11 @@ Example: | Change Wait Time | 0:00:01.500 | To give time for the mainframe screen to be "drawn" and receive the next commands, a "wait time" has been created, which by default is set to 0.5 seconds. This is a sleep applied AFTER the following keywords: - + wait_time_after_write -timedelta + To give the user time to see what is happening inside the mainframe, a "wait time after write" has @@ -215,28 +215,34 @@ Example: | Change Wait Time After Write | 0:00:02 | To give the user time to see what is happening inside the mainframe, a "wait time after write" has been created, which by default is set to 0 seconds. This is a sleep applied AFTER sending a string in these keywords: - + Close all currently opened connections and reset the index counter to 1. Close all currently opened connections and reset the index counter to 1. - + Close the current connection. Close the current connection. - + ypos -int | NoneintNone + + + + None xpos -int | NoneintNone + + + + None @@ -250,16 +256,22 @@ Example: | Delete Char | ypos=9 | xpos=25 | Delete the character under the cursor. If you want to delete a character that is in another position, simply pass the coordinates ``ypos`` / ``xpos``. - + ypos -int | NoneintNone + + + + None xpos -int | NoneintNone + + + + None @@ -274,11 +286,11 @@ Example: | Delete Field | ypos=12 | xpos=6 | Delete the entire content of a field at the current cursor location and positions the cursor at beginning of field. If you want to delete a field that is in another position, simply pass the coordinates ``ypos`` / ``xpos`` of any part in the field. - + cmd -str + Execute a [https://x3270.miraheze.org/wiki/Category:Wc3270_actions|x3270 command]. @@ -292,14 +304,18 @@ Example: | Execute Command | Scroll(forward) | # To send Page Down | Execute a [https://x3270.miraheze.org/wiki/Category:Wc3270_actions|x3270 command]. - + mode -ResultMode + As_Tuple + + + + Returns the current cursor position. The coordinates are 1 based. By default, this keyword returns a tuple of integers. However, if you specify the `mode` with the value @@ -310,20 +326,20 @@ Example: | Get Cursor Position | As Dict | # Returns a position like {"xpos": 1, "ypos": 1} | Returns the current cursor position. The coordinates are 1 based. - + string -str + mode -ResultMode + As_Tuple ignore_case -bool + False @@ -340,28 +356,28 @@ Example: | ${positions} | Get String Positions | Abc | As Dict | # Returns a list like [{"ypos": 1, "xpos": 8}] | Returns a list of tuples of ypos and xpos for the position where the `string` was found, or an empty list if it was not found. - + ypos -int + xpos -int + string -str + mode -ResultMode + As_Tuple ignore_case -bool + False @@ -378,28 +394,28 @@ Example: | ${positions} | Get String Positions Only After | 5 | 4 | Abc | As Dict | # Returns a list like [{"ypos": 5, "xpos": 5}] | Returns a list of tuples of ypos and xpos for the position where the `string` was found, but only after the specified ypos/xpos coordinates. If it is not found an empty list is returned. - + ypos -int + xpos -int + string -str + mode -ResultMode + As_Tuple ignore_case -bool + False @@ -416,15 +432,15 @@ Example: | ${positions} | Get String Positions Only Before | 11 | 20 | Abc | As Dict | # Returns a list like [{"ypos": 11, "xpos": 19}] | Returns a list of tuples of ypos and xpos for the position where the `string` was found, but only before the specified ypos/xpos coordinates. If it is not found an empty list is returned. - + ypos -int + xpos -int + Moves the cursor to the specified ypos/xpos position. The coordinates are 1 based. @@ -434,45 +450,63 @@ Example: | Move Cursor To | 1 | 5 | Moves the cursor to the specified ypos/xpos position. The coordinates are 1 based. This keyword raises an error if the specified values exceed the Mainframe screen dimensions. - + Move the cursor to the next input field. Equivalent to pressing the Tab key. Move the cursor to the next input field. Equivalent to pressing the Tab key. - + Move the cursor to the previous input field. Equivalent to pressing the Shift+Tab keys. Move the cursor to the previous input field. Equivalent to pressing the Shift+Tab keys. - - + + host -str + - -LU -str | NonestrNone + +lu + + + + None port -int + 23 extra_args -List[str] | PathLike | NoneList[str]strPathLikeNone + + + + + + + None alias -str | NonestrNone + + + + None + +utf8 + +True + + Create a connection to an IBM3270 mainframe with the default port 23. To establish a connection, only the hostname is required. Optional parameters include logical unit name (LU) and port. @@ -493,6 +527,9 @@ Arguments containing whitespace must be enclosed in single or double quotes. | -charset french | -port 992 +By default, the `utf8` argument is set to `True`, which will add the `-utf8` option to the `extra_args` list. +If you want to disable UTF-8 encoding, you can set the `utf8` argument to `False`. + Please ensure that the arguments provided are available for your specific x3270 application and version. Refer to the [https://x3270.miraheze.org/wiki/Wc3270/Command-line_options|wc3270 command line options] for a subset of available options. @@ -505,10 +542,11 @@ This keyword returns the index of the opened connection, which can be used to re when switching between connections using the `Switch Connection` keyword. For more information on opening and switching between multiple connections, please refer to the `Concurrent Connections` section. -Example: +Examples: | Open Connection | Hostname | | Open Connection | Hostname | LU=LUname | | Open Connection | Hostname | port=992 | + | Open Connection | Hostname | utf8=${False} | | @{extra_args} | Create List | -accepthostname | myhost.com | -cafile | ${CURDIR}/cafile.crt | | Append To List | ${extra_args} | -port | 992 | | Open Connection | Hostname | extra_args=${extra_args} | @@ -516,18 +554,22 @@ Example: | Open Connection | Hostname | alias=my_first_connection | Create a connection to an IBM3270 mainframe with the default port 23. To establish a connection, only the hostname is required. Optional parameters include logical unit name (LU) and port. - + session_file -PathLike + alias -str | NonestrNone + + + + None + Create a connection to an IBM3270 mainframe using a [https://x3270.miraheze.org/wiki/Session_file|session file]. @@ -555,20 +597,25 @@ where the content of `session.wc3270` is: | wc3270.model: 2 Create a connection to an IBM3270 mainframe using a [https://x3270.miraheze.org/wiki/Session_file|session file]. - + list_string -List[str]str + + + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -585,20 +632,25 @@ Example: | Page Should Contain All Strings | ${list_of_string} | error_message=New error message | Assert that all the strings in a given list exist on the mainframe screen. - + list_string -List[str]str + + + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -615,20 +667,23 @@ Example: | Page Should Contain Any String | ${list_of_string} | error_message=New error message | Assert that one of the strings in a given list exists on the mainframe screen. - + txt -str + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -653,20 +708,23 @@ Example: | Page Should Contain Match | **something** | error_message=New error message | Assert that the text displayed on the mainframe screen matches the given pattern. - + txt -str + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -683,24 +741,27 @@ Example: | Page Should Contain String | something | error_message=New error message | Assert that a given string exists on the mainframe screen. - + txt -str + number -int + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -717,11 +778,11 @@ Example: | Page Should Contain String X Times | something | 3 | error_message=New error message | Asserts that the entered string appears the desired number of times on the mainframe screen. - + regex_pattern -str + Fails if string does not match pattern as a regular expression. Regular expression check is @@ -733,20 +794,25 @@ Backslash is an escape character in the test data, and possible backslashes in t thus be escaped with another backslash (e.g. \\d\\w+). Fails if string does not match pattern as a regular expression. Regular expression check is implemented using the Python [https://docs.python.org/2/library/re.html|re module]. Python's regular expression syntax is derived from Perl, and it is thus also very similar to the syntax used, for example, in Java, Ruby and .NET. - + list_string -List[str]str + + + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -764,20 +830,25 @@ Example: | Page Should Not Contain All Strings | ${list_of_string} | error_message=New error message | Fails if one of the strings in a given list exists on the mainframe screen. If one of the string are found, the keyword will raise an exception. - + list_string -List[str]str + + + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -795,20 +866,23 @@ Example: | Page Should Not Contain Any Strings | ${list_of_string} | error_message=New error message | Assert that none of the strings in a given list exists on the mainframe screen. If one or more of the string are found, the keyword will raise an exception. - + txt -str + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -833,20 +907,23 @@ Example: | Page Should Not Contain Match | **something** | error_message=New error message | Assert that the text displayed on the mainframe screen does NOT match the given pattern. - + txt -str + ignore_case -bool + False error_message -str | NonestrNone + + + + None @@ -863,11 +940,11 @@ Example: | Page Should Not Contain String | something | error_message=New error message | Assert that a given string does NOT exist on the mainframe screen. - + regex_pattern -str + Fails if string does match pattern as a regular expression. Regular expression check is @@ -879,21 +956,22 @@ Backslash is an escape character in the test data, and possible backslashes in t thus be escaped with another backslash (e.g. \\d\\w+). Fails if string does match pattern as a regular expression. Regular expression check is implemented using the Python [https://docs.python.org/2/library/re.html|re module]. Python's regular expression syntax is derived from Perl, and it is thus also very similar to the syntax used, for example, in Java, Ruby and .NET. - + ypos -int + xpos -int + length -int + + Get a string of ``length`` at screen coordinates ``ypos`` / ``xpos``. Coordinates are 1 based, as listed in the status area of the terminal. @@ -902,9 +980,10 @@ Example for read a string in the position y=8 / x=10 of a length 15: | ${value} | Read | 8 | 10 | 15 | Get a string of ``length`` at screen coordinates ``ypos`` / ``xpos``. - + + Read the current screen and returns all content in one string. This is useful if your automation scripts should take different routes depending @@ -919,22 +998,22 @@ Example: | END | | Read the current screen and returns all content in one string. - + length -int + Similar to `Read`, however this keyword only takes `length` as an argument to get a string of length from the current cursor position. Similar to `Read`, however this keyword only takes `length` as an argument to get a string of length from the current cursor position. - + keyword -str + This keyword lets you change the keyword that runs on failure during test execution. @@ -947,17 +1026,17 @@ Example: | Register Run On Failure Keyword | Custom Keyword | # Custom Keyword is run on failure | This keyword lets you change the keyword that runs on failure during test execution. The default is `Take Screenshot`, which is set on library import. - + Send an Enter to the screen. Send an Enter to the screen. - + pf -str + Send a Program Function to the screen. @@ -966,11 +1045,11 @@ Example: | Send PF | 3 | Send a Program Function to the screen. - + path -str + Set a folder to keep the html files generated by the `Take Screenshot` keyword. @@ -982,11 +1061,14 @@ Example: | Set Screenshot Folder | C:\\Temp\\Images | Set a folder to keep the html files generated by the `Take Screenshot` keyword. - + alias_or_index -str | intstrint + + + + Switch the current connection to the one identified by index or alias. Indices are returned from @@ -1002,42 +1084,52 @@ Examples: | Switch Connection | first | | # first is now the current connection | Switch the current connection to the one identified by index or alias. Indices are returned from and aliases can be optionally provided to the `Open Connection` and `Open Connection From Session File` keywords. - - - + + + height -int -410 + +400 - + width -int -670 + +600 filename_prefix -str + screenshot + +img + +False + -Generate a screenshot of the IBM 3270 Mainframe in a html format. The + +Generate a screenshot of the IBM 3270 Mainframe in a html or png format. The default folder is the log folder of RobotFramework, if you want change see the `Set Screenshot Folder`. -The Screenshot is printed in an iframe log, with the values of height=410 and width=670, you +The Screenshot is printed in an iframe log, with the values of height=400 and width=600, you can change these values by passing them to the keyword. +Default format is html, but you can change it to png by passing img=${True} to the keyword. Because the html2image +module this option only works if chrome is installed in your system. + The file name prefix can be set, the default is "screenshot". -The full file path is returned. +The file path is returned. Example: | ${filepath} | Take Screenshot | + | ${filepath} | Take Screenshot | img=${True} | | ${filepath} | Take Screenshot | height=500 | width=700 | | Take Screenshot | height=500 | width=700 | | Take Screenshot | filename_prefix=MyScreenshot | -Generate a screenshot of the IBM 3270 Mainframe in a html format. The default folder is the log folder of RobotFramework, if you want change see the `Set Screenshot Folder`. +Generate a screenshot of the IBM 3270 Mainframe in a html or png format. The default folder is the log folder of RobotFramework, if you want change see the `Set Screenshot Folder`. - + Wait until the screen is ready, the cursor has been positioned @@ -1052,18 +1144,19 @@ Using this method tells the client to wait until a field is detected and the cursor has been positioned on it. Wait until the screen is ready, the cursor has been positioned on a modifiable field, and the keyboard is unlocked. - + txt -str + timeout -timedelta + 0:00:05 + Wait until a string exists on the mainframe screen to perform the next step. If the string does not appear in 5 seconds, the keyword will raise an exception. You can define a different timeout. @@ -1074,11 +1167,11 @@ Example: | Wait Until String | something | 0:00:15 | Wait until a string exists on the mainframe screen to perform the next step. If the string does not appear in 5 seconds, the keyword will raise an exception. You can define a different timeout. - + txt -str + Send a string *and Enter* to the screen at the current cursor location. @@ -1087,11 +1180,11 @@ Example: | Write | something | Send a string *and Enter* to the screen at the current cursor location. - + txt -str + Send only the string to the screen at the current cursor location. @@ -1100,19 +1193,19 @@ Example: | Write Bare | something | Send only the string to the screen at the current cursor location. - + txt -str + ypos -int + xpos -int + Send only the string to the screen at screen coordinates ``ypos`` / ``xpos``. @@ -1124,19 +1217,19 @@ Example: | Write Bare in Position | something | 9 | 11 | Send only the string to the screen at screen coordinates ``ypos`` / ``xpos``. - + txt -str + ypos -int + xpos -int + Send a string *and Enter* to the screen at screen coordinates ``ypos`` / ``xpos``. @@ -1148,18 +1241,34 @@ Example: | Write in Position | something | 9 | 11 | Send a string *and Enter* to the screen at screen coordinates ``ypos`` / ``xpos``. + + + +txt + + + +Send a Unicode string using the execute command String("") *and Enter* to the screen at the current +cursor location. + +Example: + | Write Unicode | ß | +Send a Unicode string using the execute command String("") *and Enter* to the screen at the current cursor location. + + + + +txt + + + +Send only the Unicode string using the execute command String("") to the screen at the current cursor location. + +Example: + | Write Unicode Bare | Æ | +Send only the Unicode string using the execute command String("") to the screen at the current cursor location. + - - - -An enumeration. - - - - - - - Strings ``TRUE``, ``YES``, ``ON`` and ``1`` are converted to Boolean ``True``, @@ -1183,6 +1292,7 @@ Examples: ``TRUE`` (converted to ``True``), ``off`` (converted to ``False``), Get String Positions Get String Positions Only After Get String Positions Only Before +Open Connection Page Should Contain All Strings Page Should Contain Any String Page Should Contain Match @@ -1192,6 +1302,27 @@ Examples: ``TRUE`` (converted to ``True``), ``off`` (converted to ``False``), Page Should Not Contain Any String Page Should Not Contain Match Page Should Not Contain String +Take Screenshot + + + +Strings must be Python [https://docs.python.org/library/stdtypes.html#dict|dictionary] +literals. They are converted to actual dictionaries using the +[https://docs.python.org/library/ast.html#ast.literal_eval|ast.literal_eval] +function. They can contain any values ``ast.literal_eval`` supports, including +dictionaries and other containers. + +If the type has nested types like ``dict[str, int]``, items are converted +to those types automatically. This in new in Robot Framework 6.0. + +Examples: ``{'a': 1, 'b': 2}``, ``{'key': 1, 'nested': {'key': 2}}`` + + +string +Mapping + + +Get Current Position @@ -1219,6 +1350,7 @@ Examples: ``42``, ``-1``, ``0b1010``, ``10 000 000``, ``0xBAD_C0FFEE`` Get String Positions Only Before Move Cursor To Open Connection +Open Connection From Session File Page Should Contain String X Times Read Read From Current Position @@ -1291,7 +1423,42 @@ Examples: ``/tmp/absolute/path``, ``relative/path/to/file.ext``, ``name.txt`` -An enumeration. +Create a collection of name/value pairs. + +Example enumeration: + +>>> class Color(Enum): +... RED = 1 +... BLUE = 2 +... GREEN = 3 + +Access them by: + +- attribute access: + + >>> Color.RED + <Color.RED: 1> + +- value lookup: + + >>> Color(1) + <Color.RED: 1> + +- name lookup: + + >>> Color['RED'] + <Color.RED: 1> + +Enumerations can be iterated over, and know how many members they have: + +>>> len(Color) +3 + +>>> list(Color) +[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>] + +Methods can be added to enumerations, and members can have their own +attributes -- see the documentation for details. string @@ -1330,6 +1497,8 @@ Examples: ``/tmp/absolute/path``, ``relative/path/to/file.ext``, ``name.txt`` Page Should Not Contain Match Page Should Not Contain String Page Should Not Match Regex +Read +Read All Screen Register Run On Failure Keyword Send PF Set Screenshot Folder @@ -1340,6 +1509,8 @@ Examples: ``/tmp/absolute/path``, ``relative/path/to/file.ext``, ``name.txt`` Write Bare Write Bare In Position Write In Position +Write Unicode +Write Unicode Bare @@ -1367,5 +1538,25 @@ for more details about the supported time formats. Wait Until String + +Strings must be Python [https://docs.python.org/library/stdtypes.html#tuple|tuple] +literals. They are converted to actual tuples using the +[https://docs.python.org/library/ast.html#ast.literal_eval|ast.literal_eval] +function. They can contain any values ``ast.literal_eval`` supports, including +tuples and other containers. + +If the type has nested types like ``tuple[str, int, int]``, items are converted +to those types automatically. This in new in Robot Framework 6.0. + +Examples: ``('one', 'two')``, ``(('one', 1), ('two', 2))`` + + +string +Sequence + + +Get Current Position + + diff --git a/pyproject.toml b/pyproject.toml index 7b3be37..447b21e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [tool.black] line-length = 120 -target = ['py37'] +target-version = ['py37'] [tool.isort] profile = 'black' line_length = 120 +lines_between_sections=0 [tool.mypy] ignore_missing_imports = true diff --git a/requirements.txt b/requirements.txt index b4d115d..8794fa3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ robotframework robotframework-pythonlibcore +html2image \ No newline at end of file diff --git a/setup.py b/setup.py index 239c2e2..151b4f4 100644 --- a/setup.py +++ b/setup.py @@ -26,9 +26,9 @@ "author_email": "samuca@gmail.com", "license": "MIT License", "license_files": ["LICENSE.md", "THIRD-PARTY-NOTICES.txt"], - "url": "https://github.com/Altran-PT-GDC/Robot-Framework-Mainframe-3270-Library", + "url": "https://github.com/MarketSquare/Robot-Framework-Mainframe-3270-Library", "packages": ["Mainframe3270", "Mainframe3270.keywords"], - "install_requires": ["robotframework", "robotframework-pythonlibcore"], + "install_requires": ["robotframework", "robotframework-pythonlibcore", "html2image"], "classifiers": [ "Development Status :: 5 - Production/Stable", "Framework :: Robot Framework", diff --git a/utest/Mainframe3270/keywords/test_assertions.py b/utest/Mainframe3270/keywords/test_assertions.py index e73c6e4..0a4a462 100644 --- a/utest/Mainframe3270/keywords/test_assertions.py +++ b/utest/Mainframe3270/keywords/test_assertions.py @@ -1,11 +1,8 @@ import re - import pytest from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270.keywords import AssertionKeywords - from .utils import create_test_object_for diff --git a/utest/Mainframe3270/keywords/test_commands.py b/utest/Mainframe3270/keywords/test_commands.py index 2d69a6f..a49d68d 100644 --- a/utest/Mainframe3270/keywords/test_commands.py +++ b/utest/Mainframe3270/keywords/test_commands.py @@ -1,13 +1,10 @@ import time - import pytest from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270.keywords import CommandKeywords from Mainframe3270.py3270 import Emulator from Mainframe3270.utils import ResultMode - from .utils import create_test_object_for diff --git a/utest/Mainframe3270/keywords/test_connection.py b/utest/Mainframe3270/keywords/test_connection.py index 186dec5..fdd3f53 100644 --- a/utest/Mainframe3270/keywords/test_connection.py +++ b/utest/Mainframe3270/keywords/test_connection.py @@ -1,14 +1,11 @@ import os from unittest.mock import mock_open, patch - import pytest from pytest_mock import MockerFixture from robot.api import logger from robot.utils import ConnectionCache - from Mainframe3270.keywords import ConnectionKeywords from Mainframe3270.py3270 import Emulator - from .utils import create_test_object_for CURDIR = os.path.dirname(os.path.realpath(__file__)) @@ -98,7 +95,7 @@ def test_open_connection_with_default_model(mocker: MockerFixture, under_test: C under_test.open_connection("myhost") - Emulator.__init__.assert_called_with(True, 30.0, [], "2") + Emulator.__init__.assert_called_with(True, 30.0, ['-utf8'], "2") def test_open_connection_with_model_from_extra_args(mocker: MockerFixture, under_test: ConnectionKeywords): diff --git a/utest/Mainframe3270/keywords/test_read_write.py b/utest/Mainframe3270/keywords/test_read_write.py index 3ba6e63..ef9694d 100644 --- a/utest/Mainframe3270/keywords/test_read_write.py +++ b/utest/Mainframe3270/keywords/test_read_write.py @@ -1,10 +1,8 @@ import pytest from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270.keywords.read_write import ReadWriteKeywords, ResultMode from Mainframe3270.py3270 import Emulator - from .utils import create_test_object_for @@ -30,6 +28,7 @@ def test_read_all_screen(under_test: ReadWriteKeywords, mocker: MockerFixture): Emulator.read_all_screen.assert_called_once() assert content == "all screen" + def test_read_all_screen_unicode(under_test: ReadWriteKeywords, mocker: MockerFixture): mocker.patch("Mainframe3270.py3270.Emulator.read_all_screen", return_value="all screen กขค") @@ -38,6 +37,7 @@ def test_read_all_screen_unicode(under_test: ReadWriteKeywords, mocker: MockerFi Emulator.read_all_screen.assert_called_once() assert content == "all screen กขค" + def test_write(mocker: MockerFixture, under_test: ReadWriteKeywords): mocker.patch("Mainframe3270.py3270.Emulator.exec_command") mocker.patch("Mainframe3270.py3270.Emulator.send_enter") @@ -47,13 +47,14 @@ def test_write(mocker: MockerFixture, under_test: ReadWriteKeywords): Emulator.exec_command.assert_called_once_with(b'String("abc")') Emulator.send_enter.assert_called_once() + def test_write_unicode(mocker: MockerFixture, under_test: ReadWriteKeywords): mocker.patch("Mainframe3270.py3270.Emulator.exec_command") mocker.patch("Mainframe3270.py3270.Emulator.send_enter") under_test.write_unicode("กขค") - Emulator.exec_command.assert_called_once_with('String("กขค")'.encode('utf-8')) + Emulator.exec_command.assert_called_once_with('String("กขค")'.encode("utf-8")) Emulator.send_enter.assert_called_once() @@ -66,13 +67,14 @@ def test_write_bare(mocker: MockerFixture, under_test: ReadWriteKeywords): Emulator.exec_command.assert_called_once_with(b'String("abc")') Emulator.send_enter.assert_not_called() + def test_write_unicode_bare(mocker: MockerFixture, under_test: ReadWriteKeywords): mocker.patch("Mainframe3270.py3270.Emulator.exec_command") mocker.patch("Mainframe3270.py3270.Emulator.send_enter") under_test.write_unicode_bare("กขค") - Emulator.exec_command.assert_called_once_with('String("กขค")'.encode('utf-8')) + Emulator.exec_command.assert_called_once_with('String("กขค")'.encode("utf-8")) Emulator.send_enter.assert_not_called() diff --git a/utest/Mainframe3270/keywords/test_screenshot.py b/utest/Mainframe3270/keywords/test_screenshot.py index 10f8fc2..ecb846c 100644 --- a/utest/Mainframe3270/keywords/test_screenshot.py +++ b/utest/Mainframe3270/keywords/test_screenshot.py @@ -1,11 +1,8 @@ import os - import pytest from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270.keywords import ScreenshotKeywords - from .utils import create_test_object_for @@ -39,16 +36,17 @@ def test_take_screenshot(mocker: MockerFixture, under_test: ScreenshotKeywords): mocker.patch("time.time", return_value=1.0) filepath = under_test.take_screenshot(500, 500) + path = os.getcwd().replace(os.sep, "/") logger.write.assert_called_with( - '', + f'', level="INFO", html=True, ) if os.name == "nt": - assert filepath == r".\screenshot_1000.html" + assert filepath == r"%s\screenshot_1000.html" % os.getcwd() else: - assert filepath == "./screenshot_1000.html" + assert filepath == f"{path}/screenshot_1000.html" def test_take_screenshot_with_filename_prefix(mocker: MockerFixture, under_test: ScreenshotKeywords): @@ -56,13 +54,14 @@ def test_take_screenshot_with_filename_prefix(mocker: MockerFixture, under_test: mocker.patch("robot.api.logger.write") mocker.patch("time.time", return_value=1.0) filepath = under_test.take_screenshot(250, 250, "MyScreenshot") + path = os.getcwd().replace(os.sep, "/") logger.write.assert_called_with( - '', + f'', level="INFO", html=True, ) if os.name == "nt": - assert filepath == r".\MyScreenshot_1000.html" + assert filepath == r"%s\MyScreenshot_1000.html" % os.getcwd() else: - assert filepath == "./MyScreenshot_1000.html" + assert filepath == f"{path}/MyScreenshot_1000.html" diff --git a/utest/Mainframe3270/keywords/test_wait_and_timeout.py b/utest/Mainframe3270/keywords/test_wait_and_timeout.py index fa5bb26..90ee4ea 100644 --- a/utest/Mainframe3270/keywords/test_wait_and_timeout.py +++ b/utest/Mainframe3270/keywords/test_wait_and_timeout.py @@ -1,9 +1,7 @@ import pytest from pytest_mock import MockerFixture - from Mainframe3270.keywords import WaitAndTimeoutKeywords from Mainframe3270.py3270 import Emulator - from .utils import create_test_object_for diff --git a/utest/Mainframe3270/test__init__.py b/utest/Mainframe3270/test__init__.py index d0869f9..1975fdb 100644 --- a/utest/Mainframe3270/test__init__.py +++ b/utest/Mainframe3270/test__init__.py @@ -1,7 +1,4 @@ import os - -from pytest_mock import MockerFixture - from Mainframe3270 import Mainframe3270 @@ -11,7 +8,7 @@ def test_default_args(): assert under_test.timeout == 30 assert under_test.wait_time == 0.5 assert under_test.wait_time_after_write == 0.0 - assert under_test.img_folder == "." + assert under_test.img_folder == os.getcwd() assert under_test.model == "2" under_test.mf is None @@ -21,20 +18,3 @@ def test_import_with_time_string(): assert under_test.timeout == 30 assert under_test.wait_time == 0.5 assert under_test.wait_time_after_write == 60 - - -def test_output_folder_robotframework_running(mocker: MockerFixture): - m_get_variable_value = mocker.patch( - "robot.libraries.BuiltIn.BuiltIn.get_variable_value", - return_value="/home/output", - ) - under_test = Mainframe3270() - - m_get_variable_value.assert_called_with("${OUTPUT DIR}") - assert under_test.output_folder == "/home/output" - - -def test_output_folder_robotframework_not_running(): - under_test = Mainframe3270() - - assert under_test.output_folder == os.getcwd() diff --git a/utest/Mainframe3270/test_librarycomponent.py b/utest/Mainframe3270/test_librarycomponent.py index 82548f7..6cd5890 100644 --- a/utest/Mainframe3270/test_librarycomponent.py +++ b/utest/Mainframe3270/test_librarycomponent.py @@ -1,3 +1,5 @@ +import os +from pathlib import Path from Mainframe3270 import Mainframe3270 from Mainframe3270.librarycomponent import LibraryComponent @@ -20,7 +22,6 @@ def test_librarycomponent_returns_common_attributes(): assert library.img_folder == under_test.img_folder assert library.cache == under_test.cache assert library.mf == under_test.mf - assert library.output_folder == under_test.output_folder assert library.model == under_test.model @@ -68,12 +69,17 @@ def test_can_set_wait_time_after_write(): assert library.wait_time_after_write == under_test.wait_time_after_write == 0.5 -def test_can_set_img_folder(): - library = Mainframe3270(img_folder="/home/myfolder") - under_test = LibraryComponent(library) +def test_can_set_img_folder(mocker): + initial_path = Path(os.getcwd()) / "my_initial_folder" + initial_path_str = str(initial_path) + new_path = Path(os.getcwd()) / "another_folder" + new_path_str = str(new_path) + mock_library = mocker.MagicMock() + mock_library.img_folder = initial_path_str + under_test = LibraryComponent(mock_library) - assert library.img_folder == under_test.img_folder == "/home/myfolder" + assert mock_library.img_folder == under_test.img_folder == initial_path_str - under_test.img_folder = "/another_folder" + under_test.img_folder = new_path_str - assert library.img_folder == under_test.img_folder == "/another_folder" + assert mock_library.img_folder == under_test.img_folder == new_path_str diff --git a/utest/Mainframe3270/test_run_on_failure.py b/utest/Mainframe3270/test_run_on_failure.py index 4b6599c..3e58a4d 100644 --- a/utest/Mainframe3270/test_run_on_failure.py +++ b/utest/Mainframe3270/test_run_on_failure.py @@ -1,7 +1,6 @@ import pytest from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270 import Mainframe3270 diff --git a/utest/Mainframe3270/test_utils.py b/utest/Mainframe3270/test_utils.py index 5fd7cb2..0cf1e88 100644 --- a/utest/Mainframe3270/test_utils.py +++ b/utest/Mainframe3270/test_utils.py @@ -1,8 +1,6 @@ from datetime import timedelta - from pytest_mock import MockerFixture from robot.api import logger - from Mainframe3270.utils import ( ResultMode, convert_timeout, diff --git a/utest/py3270/test_command.py b/utest/py3270/test_command.py index 0b89957..6915a3c 100644 --- a/utest/py3270/test_command.py +++ b/utest/py3270/test_command.py @@ -1,5 +1,4 @@ import warnings - import pytest from pytest_mock import MockerFixture diff --git a/utest/py3270/test_emulator.py b/utest/py3270/test_emulator.py index 8285c6d..b0f19e6 100644 --- a/utest/py3270/test_emulator.py +++ b/utest/py3270/test_emulator.py @@ -1,8 +1,6 @@ import errno - import pytest from pytest_mock import MockerFixture - from Mainframe3270 import py3270 from Mainframe3270.py3270 import Command, Emulator, TerminatedError diff --git a/utest/py3270/test_status.py b/utest/py3270/test_status.py index 37623b0..449b518 100644 --- a/utest/py3270/test_status.py +++ b/utest/py3270/test_status.py @@ -1,5 +1,4 @@ import pytest - from Mainframe3270.py3270 import Status