diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 47fff9b5..7a8c1a28 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,6 +26,7 @@ jobs:
xcb-util-keysyms \
xcb-util-wm \
xorg-x11-server-Xvfb \
+ tesseract \
--assumeyes
- name: Install the base dependencies
diff --git a/assets/wind.ui b/assets/wind.ui
index b2a66ef0..6116cbed 100644
--- a/assets/wind.ui
+++ b/assets/wind.ui
@@ -3004,7 +3004,7 @@
- Load
+ Scan
#arti_ccol_scan {font: 87 10pt "Font Awesome 6 Free Solid";}
@@ -4055,7 +4055,7 @@
- Load
+ Scan
#arti_gboe_scan {font: 87 10pt "Font Awesome 6 Free Solid";}
@@ -4323,7 +4323,7 @@
- Load
+ Scan
#arti_sdoe_scan {font: 87 10pt "Font Awesome 6 Free Solid";}
@@ -4591,7 +4591,7 @@
- Load
+ Scan
#arti_pmod_scan {font: 87 10pt "Font Awesome 6 Free Solid";}
@@ -4859,7 +4859,7 @@
- Load
+ Scan
#arti_fwol_scan {font: 87 10pt "Font Awesome 6 Free Solid";}
diff --git a/gi_loadouts/conf.py b/gi_loadouts/conf.py
index 0411a968..60a9f3ef 100644
--- a/gi_loadouts/conf.py
+++ b/gi_loadouts/conf.py
@@ -1,10 +1,17 @@
from platform import system
-if system() == "Windows":
- tessexec = "C:\\Program Files\\Tesseract-OCR\\tesseract.exe"
-else:
- tessexec = "/usr/bin/tesseract"
+
+def get_tessexec_path():
+ if system() == "Windows":
+ return "C:\\Program Files\\Tesseract-OCR\\tesseract.exe"
+ else:
+ return "/usr/bin/tesseract"
+
+tessexec = get_tessexec_path()
tempname = ""
temppath = ""
stattime = 5000
+
+data_prefix = "gi-loadouts-"
+data_suffix = ".traineddata"
diff --git a/gi_loadouts/face/rsrc/__init__.py b/gi_loadouts/face/rsrc/__init__.py
index 0679ec95..fc8cb0da 100644
--- a/gi_loadouts/face/rsrc/__init__.py
+++ b/gi_loadouts/face/rsrc/__init__.py
@@ -22,7 +22,7 @@ def make_temp_file() -> None:
Remove the residual cache data from the temporary directory left over during previous sessions
due to unsuccessful termination before instantiating the same for this session.
"""
- ptrn = r"gi-loadouts-[a-z0-9_]+\.traineddata"
+ ptrn = fr"{conf.data_prefix}[a-z0-9_]+\{conf.data_suffix}"
temp = Path(gettempdir())
resi = [temp / file.name for file in temp.iterdir() if file.is_file() if match(ptrn, file.name)]
@@ -45,10 +45,10 @@ def make_temp_file() -> None:
have to be created and deleted manually. On UNIX based operating systems like GNU/Linux or
MacOS, files can be reliably opened even when they have been marked for deletion.
"""
- temp = NamedTemporaryFile(prefix="gi-loadouts-", suffix=".traineddata", delete=False, mode="w+b")
+ temp = NamedTemporaryFile(prefix=conf.data_prefix, suffix=conf.data_suffix, delete=False, mode="w+b")
temp.write(cont)
temp.close()
- conf.tempname = Path(temp.name).name.replace(".traineddata", "")
+ conf.tempname = Path(temp.name).name.replace(conf.data_suffix, "")
conf.temppath = temp.name
diff --git a/gi_loadouts/face/scan/rule.py b/gi_loadouts/face/scan/rule.py
index 60bfef2c..d77f9f83 100644
--- a/gi_loadouts/face/scan/rule.py
+++ b/gi_loadouts/face/scan/rule.py
@@ -237,8 +237,18 @@ def register_return_from_scanning(self, rslt: tuple) -> None:
:return:
"""
+ area, main, seco, team, levl, rare, duration, expt = rslt
+
+ if expt:
+ self.show_dialog(
+ QMessageBox.Information,
+ "Faulty scanning",
+ f"Please consider checking your input after ensuring that the proper Tesseract OCR executable has been selected.\n\n{expt}"
+ )
+ return
+
self.arti_shot.setPixmap(self.shot)
- area, main, seco, team, levl, rare, duration = rslt
+
if area in [self.arti_dist.itemText(indx) for indx in range(self.arti_dist.count())]:
self.arti_dist.setCurrentText(area)
if team in [self.arti_type.itemText(indx) for indx in range(self.arti_type.count())]:
diff --git a/gi_loadouts/face/scan/work.py b/gi_loadouts/face/scan/work.py
index cd9f3d86..0ae4285b 100644
--- a/gi_loadouts/face/scan/work.py
+++ b/gi_loadouts/face/scan/work.py
@@ -23,7 +23,7 @@ def __init__(self, snap: ImageFile):
super().__init__()
self.snap = snap
- def scan_artifact(self) -> None:
+ def scan_artifact(self) -> None: # pragma: no cover
"""
Scan the screenshot for computing artifact information using Tesseract OCR
@@ -31,6 +31,11 @@ def scan_artifact(self) -> None:
:return: Collection of artifact information and duration of computation
"""
+ """
+ coverage.py does not seem to correctly work with the QThread.
+ Even though this part is tested but the coverage remains unchanged. https://github.com/nedbat/coveragepy/issues/686
+ """
+
strttime = time()
area, main, seco, team, levl, rare = "", ATTR(), {"a": ATTR(), "b": ATTR(), "c": ATTR(), "d": ATTR()}, "", "Level 00", "Star 0"
@@ -45,9 +50,13 @@ def scan_artifact(self) -> None:
text = image_to_string(self.snap, lang=conf.tempname, config=f"--tessdata-dir {location}")
except (OSError, TesseractError) as expt:
if isinstance(expt, OSError):
- raise OSError("Selected executable of Tesseract OCR is unfunctional.") from expt
- elif isinstance(expt, TesseractError):
- raise ValueError("Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with.") from expt
+ expt = "Selected executable of Tesseract OCR is unfunctional."
+ else:
+ expt = "Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with."
+ stoptime = time()
+ rslt = area, main, seco, team, levl, rare, (stoptime - strttime), expt
+ self.result.emit(rslt)
+ return
# DISTRIBUTION AREA
areadict = {}
@@ -148,6 +157,6 @@ def scan_artifact(self) -> None:
stoptime = time()
- rslt = area, main, seco, team, levl, rare, (stoptime - strttime)
+ rslt = area, main, seco, team, levl, rare, (stoptime - strttime), None
self.result.emit(rslt)
diff --git a/gi_loadouts/face/wind/calc.py b/gi_loadouts/face/wind/calc.py
index a4f864bc..a382764b 100644
--- a/gi_loadouts/face/wind/calc.py
+++ b/gi_loadouts/face/wind/calc.py
@@ -9,7 +9,7 @@
class Assess:
- def __init__(self) -> None:
+ def __init__(self) -> None: # pragma: no cover
self.collection = Collection()
self.c_team = None
self.c_weap = None
diff --git a/gi_loadouts/face/wind/main.py b/gi_loadouts/face/wind/main.py
index bfe449bf..2fa16636 100644
--- a/gi_loadouts/face/wind/main.py
+++ b/gi_loadouts/face/wind/main.py
@@ -82,7 +82,6 @@ def initialize_events(self) -> None:
drop, text = getattr(self, f"arti_{part}_name_{alfa}"), getattr(self, f"arti_{part}_data_{alfa}")
drop.currentTextChanged.connect(lambda _, a_drop=drop, a_text=text: self.render_lineedit_readonly_when_none(a_drop, a_text))
text.textChanged.connect(self.validate_lineedit_userdata)
- for part in ["fwol", "pmod", "sdoe", "gboe", "ccol"]:
getattr(self, f"arti_{part}_scan").clicked.connect(lambda _, a_part=part: self.show_scan_dialog(a_part))
getattr(self, f"arti_{part}_load").clicked.connect(lambda _, a_part=part: self.arti_load(a_part))
getattr(self, f"arti_{part}_save").clicked.connect(lambda _, a_part=part: self.arti_save(a_part))
diff --git a/gi_loadouts/face/wind/wind.py b/gi_loadouts/face/wind/wind.py
index 8071dae8..491cf03d 100644
--- a/gi_loadouts/face/wind/wind.py
+++ b/gi_loadouts/face/wind/wind.py
@@ -2025,7 +2025,7 @@ def retranslateUi(self, mainwind):
#endif // QT_CONFIG(tooltip)
self.arti_ccol_wipe.setText(QCoreApplication.translate("mainwind", "trash", None))
#if QT_CONFIG(tooltip)
- self.arti_ccol_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None))
+ self.arti_ccol_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None))
#endif // QT_CONFIG(tooltip)
self.arti_ccol_scan.setText(QCoreApplication.translate("mainwind", "eye", None))
self.arti_ccol_data_d.setPlaceholderText(QCoreApplication.translate("mainwind", "Data", None))
@@ -2057,7 +2057,7 @@ def retranslateUi(self, mainwind):
#endif // QT_CONFIG(tooltip)
self.arti_gboe_wipe.setText(QCoreApplication.translate("mainwind", "trash", None))
#if QT_CONFIG(tooltip)
- self.arti_gboe_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None))
+ self.arti_gboe_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None))
#endif // QT_CONFIG(tooltip)
self.arti_gboe_scan.setText(QCoreApplication.translate("mainwind", "eye", None))
#if QT_CONFIG(tooltip)
@@ -2077,7 +2077,7 @@ def retranslateUi(self, mainwind):
#endif // QT_CONFIG(tooltip)
self.arti_sdoe_wipe.setText(QCoreApplication.translate("mainwind", "trash", None))
#if QT_CONFIG(tooltip)
- self.arti_sdoe_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None))
+ self.arti_sdoe_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None))
#endif // QT_CONFIG(tooltip)
self.arti_sdoe_scan.setText(QCoreApplication.translate("mainwind", "eye", None))
#if QT_CONFIG(tooltip)
@@ -2097,7 +2097,7 @@ def retranslateUi(self, mainwind):
#endif // QT_CONFIG(tooltip)
self.arti_pmod_wipe.setText(QCoreApplication.translate("mainwind", "trash", None))
#if QT_CONFIG(tooltip)
- self.arti_pmod_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None))
+ self.arti_pmod_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None))
#endif // QT_CONFIG(tooltip)
self.arti_pmod_scan.setText(QCoreApplication.translate("mainwind", "eye", None))
#if QT_CONFIG(tooltip)
@@ -2117,7 +2117,7 @@ def retranslateUi(self, mainwind):
#endif // QT_CONFIG(tooltip)
self.arti_fwol_wipe.setText(QCoreApplication.translate("mainwind", "trash", None))
#if QT_CONFIG(tooltip)
- self.arti_fwol_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None))
+ self.arti_fwol_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None))
#endif // QT_CONFIG(tooltip)
self.arti_fwol_scan.setText(QCoreApplication.translate("mainwind", "eye", None))
self.pair_area_desc.setDocumentTitle(QCoreApplication.translate("mainwind", "pair_area_desc", None))
diff --git a/test/conftest.py b/test/conftest.py
index 4b0e4748..33ba8069 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,5 +1,6 @@
import pytest
+from gi_loadouts.face.scan.main import ScanDialog
from gi_loadouts.face.wind.main import MainWindow
@@ -8,3 +9,14 @@ def runner(qtbot):
testwind = MainWindow()
qtbot.addWidget(testwind)
return testwind
+
+
+@pytest.fixture
+def scantest(qtbot):
+ """
+ The codebase will automatically detect the part once the artifact is changed manually in the
+ window or changed by the after the scan
+ """
+ testscan = ScanDialog("fwol")
+ qtbot.addWidget(testscan)
+ return testscan
diff --git a/test/face/scan/__init__.py b/test/face/scan/__init__.py
new file mode 100644
index 00000000..20e0b18f
--- /dev/null
+++ b/test/face/scan/__init__.py
@@ -0,0 +1,32 @@
+from gi_loadouts.type.arti.base import (
+ MainStatType_CCOL,
+ MainStatType_FWOL,
+ MainStatType_GBOE,
+ MainStatType_PMOD,
+ MainStatType_SDOE,
+)
+from gi_loadouts.type.stat import ATTR, STAT
+
+__dist__ = {
+ "Flower of Life": {"list": MainStatType_FWOL, "part": "fwol"},
+ "Plume of Death": {"list": MainStatType_PMOD, "part": "pmod"},
+ "Sands of Eon": {"list": MainStatType_SDOE, "part": "sdoe"},
+ "Goblet of Eonothem": {"list": MainStatType_GBOE, "part": "gboe"},
+ "Circlet of Logos": {"list": MainStatType_CCOL, "part": "ccol"},
+}
+
+__rtrn__ = {
+ "part": "sdoe",
+ "team": "Shimenawa's Reminiscence",
+ "rare": "Star 5",
+ "levl": "Level 20",
+ "stat": {
+ "main": ATTR(stat_name=STAT.energy_recharge_perc, stat_data=51.8),
+ "seco": {
+ "a": ATTR(stat_name=STAT.elemental_mastery, stat_data=23.0),
+ "b": ATTR(stat_name=STAT.critical_rate_perc, stat_data=6.6),
+ "c": ATTR(stat_name=STAT.critical_damage_perc, stat_data=21.0),
+ "d": ATTR(stat_name=STAT.health_points_perc, stat_data=13.4)
+ }
+ }
+}
diff --git a/test/face/scan/test_scan.py b/test/face/scan/test_scan.py
new file mode 100644
index 00000000..ed9b9979
--- /dev/null
+++ b/test/face/scan/test_scan.py
@@ -0,0 +1,490 @@
+from os import remove
+from random import choice, randbytes
+from tempfile import NamedTemporaryFile
+from uuid import uuid4
+
+import pytest
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
+from pytesseract import TesseractError
+
+from gi_loadouts import __versdata__, conf
+from gi_loadouts.data.arti import __artilist__
+from gi_loadouts.face.rsrc import kill_temp_file, make_temp_file
+from gi_loadouts.face.scan import file
+from gi_loadouts.face.util import truncate_text
+from gi_loadouts.type.arti import ArtiLevl, base
+from gi_loadouts.type.stat import STAT
+from test.face.scan import __dist__, __rtrn__
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan: Testing ScanWindow")
+ ]
+)
+def test_scan_window(scantest, _) -> None:
+ """
+ Testing ScanWindow
+
+ :return:
+ """
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert isinstance(scantest, QDialog)
+ assert scantest.windowTitle() == f"Loadouts for Genshin Impact v{__versdata__}"
+
+
+@pytest.mark.parametrize(
+ "name, rare, part, part_type",
+ [
+ pytest.param(
+ name, team.rare, team.fwol, "Flower of Life", id=f"face.scan.rule: Configuring Flower of Life artifact - {name}"
+ ) for name, team in __artilist__.items()
+ ] +
+ [
+ pytest.param(
+ name, team.rare, team.pmod, "Plume of Death", id=f"face.scan.rule: Configuring Plume of Death artifact - {name}"
+ ) for name, team in __artilist__.items()
+ ] +
+ [
+ pytest.param(
+ name, team.rare, team.sdoe, "Sands of Eon", id=f"face.scan.rule: Configuring Sands of Eon artifact - {name}"
+ ) for name, team in __artilist__.items()
+ ] +
+ [
+ pytest.param(
+ name, team.rare, team.gboe, "Goblet of Eonothem", id=f"face.scan.rule: Configuring Goblet of Eonothem artifact - {name}"
+ ) for name, team in __artilist__.items()
+ ] +
+ [
+ pytest.param(
+ name, team.rare, team.ccol, "Circlet of Logos", id=f"face.scan.rule: Configuring Circlet of Logos artifact - {name}"
+ ) for name, team in __artilist__.items()
+ ]
+)
+def test_scan_arti_drop(scantest, name, rare, part, part_type) -> None:
+ """
+ Test the configuration of artifact on the user interface
+
+ :return:
+ """
+
+ """
+ Set the user interface elements as intended
+ """
+ conf = dict()
+
+ conf["dist"] = part_type
+ scantest.arti_dist.setCurrentText(conf["dist"])
+ conf["name"] = name
+ scantest.arti_type.setCurrentText(conf["name"])
+ conf["rare"] = choice([item for item in rare])
+ scantest.arti_rare.setCurrentText(conf["rare"].value.name)
+ conf["levl"] = choice([item for item in ArtiLevl if conf["rare"] in item.value.rare])
+ scantest.arti_levl.setCurrentText(conf["levl"].value.name)
+ conf["stat"] = choice([item for item in getattr(base, __dist__[conf["dist"]]["list"].__name__) if item.value != STAT.none])
+ scantest.arti_name_main.setCurrentText(conf["stat"].value.value)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ if name == "None":
+ return
+
+ assert scantest.arti_type_name.text() == truncate_text(part.__name__, 34)
+ assert scantest.arti_data_main.text() == str(round(part.stat_data, 1))
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Clearing artifact")
+ ]
+)
+def test_scan_arti_wipe(scantest, qtbot, _) -> None:
+ """
+ Test the clearing of the artifact
+
+ :return:
+ """
+
+ """
+ Set the user interface elements as intended
+ """
+ qtbot.mouseClick(scantest.arti_back_wipe, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert scantest.arti_type.currentText() == "None"
+ assert scantest.arti_levl.currentText() == "None"
+ assert scantest.arti_rare.currentText() == "Star 0"
+ assert scantest.arti_type_name.text() == "None"
+ for item in ["main", "a", "b", "c", "d"]:
+ assert getattr(scantest, f"arti_name_{item}").currentText() == "None"
+ assert getattr(scantest, f"arti_data_{item}").text() == ""
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Test OCR functionality")
+ ]
+)
+def test_scan_arti_load(scantest, qtbot, mocker, _) -> None:
+ """
+ Test OCR functionality
+
+ :return:
+ """
+
+ """
+ Create the Tesseract training data
+ """
+ mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-")
+ make_temp_file()
+
+ """
+ Perform the action of loading the artifact information
+ """
+ tempfile = "test/static/img/gi-loadouts-ocr-test.webp"
+ savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test-", suffix=".webp", delete=False, mode="wb")
+ with open(tempfile, "rb") as src_file:
+ savefile.write(src_file.read())
+ savefile.close()
+ mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, ""))
+ qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ def check_label():
+ """
+ Checking the interface elements change asynchronously
+ """
+ assert scantest.arti_text.text() == "Browse your local storage to load a high quality screenshot of your artifact and the statistics will automatically be computed from there."
+ assert scantest.arti_type.currentText() == "Shimenawa's Reminiscence"
+ assert scantest.arti_levl.currentText() == "Level 20"
+ assert scantest.arti_rare.currentText() == "Star 5"
+ assert scantest.arti_type_name.text() == "Morning Dew's Moment"
+ assert scantest.arti_name_main.currentText() == "Energy Recharge"
+ assert scantest.arti_data_main.text() == "51.8"
+ assert scantest.arti_name_a.currentText() == "Elemental Mastery"
+ assert scantest.arti_data_a.text() == "23.0"
+ assert scantest.arti_name_b.currentText() == "Crit Rate"
+ assert scantest.arti_data_b.text() == "6.6"
+ assert scantest.arti_name_c.currentText() == "Crit DMG"
+ assert scantest.arti_data_c.text() == "21.0"
+ assert scantest.arti_name_d.currentText() == "HP %"
+ assert scantest.arti_data_d.text() == "13.4"
+
+ qtbot.waitUntil(check_label)
+
+ """
+ Cleanup the temporary files from temp directory
+ """
+ remove(savefile.name)
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Cancelling loading an artifact snapshot")
+ ]
+)
+def test_scan_arti_load_nope(scantest, qtbot, mocker, _) -> None:
+ """
+ Attempt cancelling loading an artifact snapshot
+
+ :return:
+ """
+
+ """
+ Store the artifact information before trying to load
+ """
+ init_arti_type = scantest.arti_type.currentText()
+ init_arti_levl = scantest.arti_levl.currentText()
+ init_arti_rare = scantest.arti_rare.currentText()
+ init_arti_type_name = scantest.arti_type_name.text()
+ init_arti_name_main = scantest.arti_name_main.currentText()
+ init_arti_data_main = scantest.arti_data_main.text()
+ init_arti_name_a = scantest.arti_name_a.currentText()
+ init_arti_data_a = scantest.arti_data_a.text()
+ init_arti_name_b = scantest.arti_name_b.currentText()
+ init_arti_data_b = scantest.arti_data_b.text()
+ init_arti_name_c = scantest.arti_name_c.currentText()
+ init_arti_data_c = scantest.arti_data_c.text()
+ init_arti_name_d = scantest.arti_name_d.currentText()
+ init_arti_data_d = scantest.arti_data_d.text()
+
+ """
+ Perform the action of loading the artifact information
+ """
+ mocker.patch.object(file.FileHandling, "load_screenshot", return_value=(False, None, None))
+ qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements stayed the same
+ """
+ assert scantest.arti_type.currentText() == init_arti_type
+ assert scantest.arti_levl.currentText() == init_arti_levl
+ assert scantest.arti_rare.currentText() == init_arti_rare
+ assert scantest.arti_type_name.text() == init_arti_type_name
+ assert scantest.arti_name_main.currentText() == init_arti_name_main
+ assert scantest.arti_data_main.text() == init_arti_data_main
+ assert scantest.arti_name_a.currentText() == init_arti_name_a
+ assert scantest.arti_data_a.text() == init_arti_data_a
+ assert scantest.arti_name_b.currentText() == init_arti_name_b
+ assert scantest.arti_data_b.text() == init_arti_data_b
+ assert scantest.arti_name_c.currentText() == init_arti_name_c
+ assert scantest.arti_data_c.text() == init_arti_data_c
+ assert scantest.arti_name_d.currentText() == init_arti_name_d
+ assert scantest.arti_data_d.text() == init_arti_data_d
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Failing to load the artifact snapshot")
+ ]
+)
+def test_scan_arti_load_fail(scantest, qtbot, mocker, _) -> None:
+ """
+ Attempt failing to loading the artifact snapshot
+
+ :return:
+ """
+
+ """
+ Create a temporary file filled with random data to simulate a failure when trying to open an
+ image using PIL.Image.open().
+ """
+ savefile = NamedTemporaryFile(prefix="gi-loadouts-", suffix=".webp", delete=False, mode="wb")
+ savefile.write(randbytes(512*1024))
+ savefile.close()
+
+ """
+ Perform the action of loading the artifact snapshot
+ """
+ mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, ""))
+ qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert isinstance(scantest.dialog, QMessageBox)
+ assert scantest.dialog.icon() == QMessageBox.Information
+ assert scantest.dialog.windowTitle() == "Faulty scanning"
+ assert "Please select an accurate screenshot." in scantest.dialog.text()
+ assert scantest.dialog.isVisible()
+
+ """
+ Cleanup the temporary files from temp directory
+ """
+ remove(savefile.name)
+
+
+@pytest.mark.parametrize(
+ "platform, tempexec",
+ [
+ pytest.param("Linux", "/usr/bin/tesseract", id="face.scan.rule: Actual loading of Tesseract OCR executable in Linux"),
+ pytest.param("Windows", "C:\\Program Files\\Tesseract-OCR\\tesseract.exe", id="face.scan.rule: Actual loading of Tesseract OCR executable in Windows")
+ ]
+)
+def test_scan_tessexec_load(scantest, qtbot, mocker, platform, tempexec) -> None:
+ """
+ Attempt actual loading of Tesseract OCR executable
+
+ :return:
+ """
+
+ """
+ Store the initial conf.tessexec so that after this test it can be restored to the original value
+ """
+ temp = conf.tessexec
+
+ """
+ Mock the system environment and reload the tessexec variable to apply the mocked path
+ """
+ mocker.patch("gi_loadouts.conf.system", return_value=platform)
+ conf.tessexec = conf.get_tessexec_path()
+
+ """
+ Perform the action of loading the actual Tesseract OCR executable
+ """
+ mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(tempexec, ""))
+ qtbot.mouseClick(scantest.arti_cnvs_conf, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert conf.tessexec == tempexec
+
+ """
+ Reinstate the path for Tesseract OCR executable
+ """
+ conf.tessexec = temp
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Failing to load the Tesseract OCR executable")
+ ]
+)
+def test_scan_tessexec_load_fail(scantest, qtbot, mocker, _) -> None:
+ """
+ Attempt failing to load the Tesseract OCR executable
+
+ :return:
+ """
+
+ """
+ Perform the action of loading the Tesseract OCR executable
+ """
+ mocker.patch.object(file.FileHandling, "load_tessexec", side_effect=Exception)
+ qtbot.mouseClick(scantest.arti_cnvs_conf, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert isinstance(scantest.dialog, QMessageBox)
+ assert scantest.dialog.icon() == QMessageBox.Information
+ assert scantest.dialog.windowTitle() == "Faulty scanning"
+ assert "Please consider checking your input after ensuring that the proper Tesseract OCR executable has been selected." in scantest.dialog.text()
+ assert scantest.dialog.isVisible()
+
+
+@pytest.mark.parametrize(
+ "expt",
+ [
+ pytest.param(OSError, id="face.scan.rule: Failing of register function with OSError"),
+ pytest.param(TesseractError("abc", "xyz"), id="face.scan.rule: Failing of register function with TesseractError")
+ ]
+)
+def test_scan_register_fail(scantest, qtbot, mocker, expt) -> None:
+ """
+ Attempt failing the register function with OSError and TesseractError
+
+ :return:
+ """
+
+ """
+ Create the tesseract training data
+ """
+ mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-")
+ make_temp_file()
+
+ """
+ Perform the action of loading the artifact information
+ """
+ tempfile = "test/static/img/gi-loadouts-ocr-test.webp"
+ savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test-", suffix=".webp", delete=False, mode="wb")
+ with open(tempfile, "rb") as src_file:
+ savefile.write(src_file.read())
+ savefile.close()
+ mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, ""))
+ mocker.patch("gi_loadouts.face.scan.work.image_to_string", side_effect=expt)
+ qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ def check_label():
+ """
+ Checking the interface elements change asynchronously
+ """
+ assert isinstance(scantest.dialog, QMessageBox)
+ assert scantest.dialog.icon() == QMessageBox.Information
+ assert scantest.dialog.windowTitle() == "Faulty scanning"
+ if isinstance(expt, OSError):
+ assert "Selected executable of Tesseract OCR is unfunctional." in scantest.dialog.text()
+ elif isinstance(expt, TesseractError):
+ assert "Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with." in scantest.dialog.text()
+ assert scantest.dialog.isVisible()
+
+ qtbot.waitUntil(check_label)
+
+ """
+ Cleanup the temporary files from temp directory
+ """
+ kill_temp_file()
+ remove(savefile.name)
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Clearing Snapshot")
+ ]
+)
+def test_scan_snapshot_wipe(scantest, qtbot, _) -> None:
+ """
+ Attempt clearing of the snapshot
+
+ :return:
+ """
+
+ """
+ Perform the action to clear the snapshot
+ """
+ qtbot.mouseClick(scantest.arti_cnvs_wipe, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert scantest.arti_shot.text() == "YOUR ARTIFACT SCREENSHOT WILL SHOW UP HERE"
+
+
+@pytest.mark.parametrize(
+ "_",
+ [
+ pytest.param(None, id="face.scan.rule: Importing artifact information in MainWindow")
+ ]
+)
+def test_scan_import_arti(scantest, qtbot, mocker, _) -> None:
+ """
+ Attempt importing artifact information in MainWindow
+
+ :return:
+ """
+
+ """
+ Create the tesseract training data
+ """
+ mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-")
+ make_temp_file()
+
+ """
+ Perform the action of loading the artifact information
+ """
+ tempfile = "test/static/img/gi-loadouts-ocr-test.webp"
+ savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test-", suffix=".webp", delete=False, mode="wb")
+ with open(tempfile, "rb") as src_file:
+ savefile.write(src_file.read())
+ savefile.close()
+ mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, ""))
+ qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton)
+
+ init = scantest.arti_type.currentText()
+ qtbot.waitUntil(lambda: scantest.arti_type.currentText() != init)
+
+ """
+ Perform the action of importing artifact info in MainWindow
+ """
+ qtbot.mouseClick(scantest.arti_back_done, Qt.LeftButton)
+
+ """
+ Confirm if the user interface elements change accordingly
+ """
+ assert scantest.keep_info() == __rtrn__
+
+ """
+ Cleanup the temporary files from temp directory
+ """
+ kill_temp_file()
+ remove(savefile.name)
diff --git a/test/static/img/gi-loadouts-ocr-test.webp b/test/static/img/gi-loadouts-ocr-test.webp
new file mode 100644
index 00000000..c7256fc7
Binary files /dev/null and b/test/static/img/gi-loadouts-ocr-test.webp differ