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 @@
-
+
-
+ {{/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.2Mainframe3270 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
+Truetimeout
-timedelta
+0:00:30wait_time
-timedelta
+0:00:00.500000wait_time_after_write
-timedelta
+0:00:00img_folder
-str
+.run_on_failure_keyword
-str
+Take Screenshotmodel
-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
+
+
+
+Nonexpos
-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
+
+
+
+Nonexpos
-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_Tupleignore_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_Tupleignore_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_Tupleignore_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
+
+
+
+Noneport
-int
+23extra_args
-List[str] | PathLike | NoneList[str]strPathLikeNone
+
+
+
+
+
+
+Nonealias
-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: 2Create 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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+Falseerror_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
+
+600filename_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 PositionsGet String Positions Only AfterGet String Positions Only Before
+Open ConnectionPage Should Contain All StringsPage Should Contain Any StringPage Should Contain Match
@@ -1192,6 +1302,27 @@ Examples: ``TRUE`` (converted to ``True``), ``off`` (converted to ``False``),
Page Should Not Contain Any StringPage Should Not Contain MatchPage 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 BeforeMove Cursor ToOpen Connection
+Open Connection From Session FilePage Should Contain String X TimesReadRead 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 MatchPage Should Not Contain StringPage Should Not Match Regex
+Read
+Read All ScreenRegister Run On Failure KeywordSend PFSet Screenshot Folder
@@ -1340,6 +1509,8 @@ Examples: ``/tmp/absolute/path``, ``relative/path/to/file.ext``, ``name.txt``
Write BareWrite Bare In PositionWrite 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