-
-
-
-
-
\ No newline at end of file
diff --git a/examples/web_ui/splinter/test_splinter_integration.py b/examples/web_ui/splinter/test_splinter_integration.py
index 9d3c1ba..0941d83 100644
--- a/examples/web_ui/splinter/test_splinter_integration.py
+++ b/examples/web_ui/splinter/test_splinter_integration.py
@@ -14,7 +14,7 @@ class TestSplinterIntegration:
"""
def setup_method(self, method):
- file_path = pathlib.Path(__file__).parent.resolve()
+ file_path = pathlib.Path(__file__).parent.parent.resolve()
browser = Browser("chrome", headless=True)
self._app = Application(browser)
self._app.at(setup.OpenSplinterApp, url=f"file:///{file_path}/sample.html")
diff --git a/guara/assertion.py b/guara/assertion.py
index d54112e..47966b6 100644
--- a/guara/assertion.py
+++ b/guara/assertion.py
@@ -52,6 +52,8 @@ def validates(self, actual: Any, expected: Any) -> None:
self.asserts(actual, expected)
LOGGER.info(f" Actual : {actual}")
LOGGER.info(f" Expected: {expected}")
- except Exception:
+ except Exception as e:
LOGGER.error(f" Actual : {actual}")
LOGGER.error(f" Expected: {expected}")
+ LOGGER.exception(str(e))
+ raise
diff --git a/guara/asynchronous/assertion.py b/guara/asynchronous/assertion.py
index 399ad8c..e372ca4 100644
--- a/guara/asynchronous/assertion.py
+++ b/guara/asynchronous/assertion.py
@@ -56,7 +56,8 @@ async def validates(self, actual: Any, expected: Any) -> None:
"""
try:
await self.asserts(actual, expected)
- except Exception:
+ except Exception as e:
LOGGER.error(f" Actual : {actual.result}")
LOGGER.error(f" Expected: {expected}")
+ LOGGER.exception(str(e))
raise
diff --git a/guara/asynchronous/transaction.py b/guara/asynchronous/transaction.py
index cfdb8af..ba91b93 100644
--- a/guara/asynchronous/transaction.py
+++ b/guara/asynchronous/transaction.py
@@ -16,7 +16,7 @@ class Application:
The runner of the automation.
"""
- def __init__(self, driver: Any):
+ def __init__(self, driver: Any = None):
"""
Initializing the application with a driver.
From 3de856a569886246e037bfb9eec34bc136f9be6e Mon Sep 17 00:00:00 2001
From: Douglas Cardoso <29078346+douglasdcm@users.noreply.github.com>
Date: Tue, 21 Jan 2025 16:17:12 -0300
Subject: [PATCH 2/3] tutorial-chatgpt
---
README.md | 3 +
docs/CHATGPT_ASSISTANCE.md | 284 +++++++++++++++++++++++++++++++
docs/images/copy_raw_content.png | Bin 0 -> 2222 bytes
3 files changed, 287 insertions(+)
create mode 100644 docs/CHATGPT_ASSISTANCE.md
create mode 100644 docs/images/copy_raw_content.png
diff --git a/README.md b/README.md
index d6edd46..b7ecfea 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ The scarlet ibis, sometimes called red ibis (Eudocimus ruber), is a species of i
- [Tutorial](#Tutorial)
- [Using other Web Drivers](#Using-other-Web-Drivers)
- [Asynchronous execution](#Asynchronous-execution)
+- [ChatGPT assistance](#ChatGPT-assistance)
- [Contributing](#Contributing)
# Syntax
@@ -185,6 +186,8 @@ It is possible to run Guara using other Web Drivers like [Caqui](https://github.
# Asynchronous execution
The core code was extended to allow asynchronous executions. Get more details [here](https://github.com/douglasdcm/guara/tree/main/docs/ASYNC.md)
+# ChatGPT assistance
+It is possible to use [ChatGPT](https://chatgpt.com/) to help you organize your code in `Page Transactions` pattern. Check these [simple steps](https://github.com/douglasdcm/guara/blob/main/docs/CHATGPT_ASSISTANCE.md).
# How you can help?
diff --git a/docs/CHATGPT_ASSISTANCE.md b/docs/CHATGPT_ASSISTANCE.md
new file mode 100644
index 0000000..8404603
--- /dev/null
+++ b/docs/CHATGPT_ASSISTANCE.md
@@ -0,0 +1,284 @@
+# Using ChatGPT with Page Transactions
+
+As `Page Transactions` pattern is a new term. ChatGPT will not find a reference to it. But, it is not an excuse to not use the pattern. Teaching ChatGPT about the pattern is very easy and fast and the result is pretty much accurate.
+
+1. Go to [ChatGPT](https://chatgpt.com/) page and login with your account. Create a new one if necessary.
+2. Go to [Guara README.md](https://github.com/douglasdcm/guara/blob/main/README.md) and copy the raw content 
+3. Paste the following statements in the ChatGPT prompt
+```
+Given the explanation of the pattern Page Transactions after the dots let me know what you understood
+...
+
+```
+4. Check it understanding and continue
+5. Now that ChatGPT knows about the new pattern. Ask it to organize your code using it
+```
+Based on your understanding about the pattern Page Transactions organize the code after the dots
+...
+
+```
+6. Check if
+
+- `guara` is part of the imports
+- `Application` is used to orchestrate the code execution with `at` and `asserts`
+7. Make necessary adjustments if necessary
+8. Done! Your code is in the new pattern.
+
+# Here is a practical example
+
+## Raw code in Selenium
+
+```python
+import random
+import pathlib
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+
+def test_sample_page():
+ file_path = pathlib.Path(__file__).parent.resolve()
+ driver = webdriver.Chrome()
+
+ driver.get(f"file:///{file_path}/sample.html")
+
+ title = driver.title
+ # assert title == "Web form"
+ assert title == "Sample page"
+
+ driver.implicitly_wait(0.5)
+
+ # text_box = driver.find_element(by=By.NAME, value="input")
+ text_box = driver.find_element(by=By.ID, value="input")
+ submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")
+
+ text = ["cheese", "selenium", "test", "bla", "foo"]
+ text_box.send_keys(text[random.randrange(len(text))])
+ from_input = text_box.get_attribute("value")
+ submit_button.click()
+
+ message = driver.find_element(by=By.ID, value="result")
+ value = message.text
+ # assert value == "It workls! Selenium!"
+ assert value == f"It works! {from_input}!"
+
+ driver.quit()
+```
+
+## It is the output of ChatGPT
+
+To organize this code following the Page Transactions pattern, we need to abstract transactions (e.g., opening the page, interacting with input fields, and validating results) into reusable classes that inherit from AbstractTransaction. The test logic should then use the Application class to orchestrate these transactions.
+
+Here's the organized code:
+
+```python
+import random
+import pathlib
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from guara.transaction import AbstractTransaction, Application
+from guara import it, setup
+
+
+# Transaction: Open the sample page
+class OpenSamplePage(AbstractTransaction):
+ def __init__(self, driver, file_path):
+ super().__init__(driver)
+ self.file_path = file_path
+
+ def do(self, **kwargs):
+ self._driver.get(f"file:///{self.file_path}/sample.html")
+ self._driver.implicitly_wait(0.5)
+ return self._driver.title
+
+
+# Transaction: Submit text to the form
+class SubmitText(AbstractTransaction):
+ def do(self, **kwargs):
+ text = kwargs.get("text", "")
+ text_box = self._driver.find_element(by=By.ID, value="input")
+ submit_button = self._driver.find_element(by=By.CSS_SELECTOR, value="button")
+
+ text_box.send_keys(text)
+ from_input = text_box.get_attribute("value")
+ submit_button.click()
+ return from_input
+
+
+# Transaction: Validate the result message
+class ValidateResult(AbstractTransaction):
+ def do(self, **kwargs):
+ expected_text = kwargs.get("expected_text", "")
+ message = self._driver.find_element(by=By.ID, value="result")
+ return message.text == f"It works! {expected_text}!"
+
+
+# Test Case
+def test_sample_page():
+ file_path = pathlib.Path(__file__).parent.resolve()
+ driver = webdriver.Chrome()
+
+ # Instantiate the Application
+ app = Application(driver)
+
+ # Open the sample page and assert the title
+ title = app.at(OpenSamplePage, file_path=file_path).result
+ app.asserts(it.IsEqualTo, "Sample page")
+
+ # Submit text and validate the result
+ text_choices = ["cheese", "selenium", "test", "bla", "foo"]
+ selected_text = random.choice(text_choices)
+
+ app.at(SubmitText, text=selected_text)
+ app.at(ValidateResult, expected_text=selected_text).asserts(it.IsTrue)
+
+ # Close the application
+ app.at(setup.CloseApp)
+
+```
+
+Explanation:
+
+Transactions:
+ OpenSamplePage: Handles the logic to open the web page and optionally returns the page title.
+ SubmitText: Interacts with the input field and submits the form, returning the entered text.
+ ValidateResult: Validates that the result message matches the expected format.
+
+Application:
+ Orchestrates the transactions using the at method and applies assertions using the asserts method.
+
+Test Flow:
+ The test script uses Application to execute transactions in a logical sequence.
+ Assertions are modular, readable, and reusable.
+
+Reusability:
+ The transaction classes are reusable for other tests involving similar interactions. This modular approach reduces duplication and enhances maintainability.
+
+Let me know if you'd like further adjustments!
+
+## Make necessary adjustments
+
+Notice the class `OpenSamplePage` has the parameter `file_path` with is not following the functional usage of the pattern and the assertion `it.IsTrue` does not exist in Guara core (yet). The new assertion introduced by the AI was a good induction.
+
+It is still possible to use ChatGPT to fix the issues. For example:
+
+```
+The code has some inconsistencies listed after the dots. Based on the list, give corrections in the code and outputs it to me
+....
+- The transaction constructors does not allow other parameters than the `driver`. Any necessary parameters should be placed in the 'do' method
+- The module 'gura.it' has no function 'IsTrue'. Check the explanation after the slashes to know how to build a new custom assertion 'IsTrue' and replace 'it.IsTrue' by 'IsTrue'
+
+ \\\\\
+# Got from https://github.com/douglasdcm/guara/blob/main/docs/TUTORIAL.md#extending-assertions
+
+The strategies to assert the result can e extended by inheriting from IAssertion as following:
+
+# The code is the same of the previous session.
+# The unnecessary details were not duplicated
+# import IAssertion
+from guara.transaction import Application, IAssertion
+
+# Implements a new assertion to check variations of the expected value
+class IsEqualToVariationsOf(IAssertion):
+ def __init__(self):
+ super().__init__()
+
+ def asserts(self, actual, expected):
+ if actual.lower() == expected.lower():
+ return True
+ raises AssertionError
+
+def test_google_search(setup_app):
+ app = setup_app
+
+ # The new assertion is used to check variations of 'All', like 'aLL' or 'ALL'
+ app.at(home.Search, text="guara").asserts(IsEqualToVariationsOf, "All")
+```
+
+### The output
+
+Here is the corrected and updated code based on the feedback:
+Updates:
+
+Transaction Constructors: Now accept only the driver parameter. Any additional parameters are passed to the do method.
+Custom Assertion (IsTrue): A new custom assertion class is implemented, inheriting from IAssertion. This replaces the invalid it.IsTrue.
+
+Corrected Code:
+
+```python
+import random
+import pathlib
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from guara.transaction import AbstractTransaction, Application, IAssertion
+from guara import it, setup
+
+
+# Custom Assertion: Checks if the actual value is True
+class IsTrue(IAssertion):
+ def __init__(self):
+ super().__init__()
+
+ def asserts(self, actual, expected=None):
+ if actual is not True:
+ raise AssertionError(f"Expected True but got {actual}")
+
+
+# Transaction: Open the sample page
+class OpenSamplePage(AbstractTransaction):
+ def do(self, **kwargs):
+ file_path = kwargs.get("file_path")
+ self._driver.get(f"file:///{file_path}/sample.html")
+ self._driver.implicitly_wait(0.5)
+ return self._driver.title
+
+
+# Transaction: Submit text to the form
+class SubmitText(AbstractTransaction):
+ def do(self, **kwargs):
+ text = kwargs.get("text", "")
+ text_box = self._driver.find_element(by=By.ID, value="input")
+ submit_button = self._driver.find_element(by=By.CSS_SELECTOR, value="button")
+
+ text_box.send_keys(text)
+ from_input = text_box.get_attribute("value")
+ submit_button.click()
+ return from_input
+
+
+# Transaction: Validate the result message
+class ValidateResult(AbstractTransaction):
+ def do(self, **kwargs):
+ expected_text = kwargs.get("expected_text", "")
+ message = self._driver.find_element(by=By.ID, value="result")
+ actual_text = message.text
+ return actual_text == f"It works! {expected_text}!"
+
+
+# Test Case
+def test_sample_page():
+ file_path = pathlib.Path(__file__).parent.resolve()
+ driver = webdriver.Chrome()
+
+ # Instantiate the Application
+ app = Application(driver)
+
+ # Open the sample page and assert the title
+ title = app.at(OpenSamplePage, file_path=str(file_path)).result
+ app.asserts(it.IsEqualTo, "Sample page")
+
+ # Submit text and validate the result
+ text_choices = ["cheese", "selenium", "test", "bla", "foo"]
+ selected_text = random.choice(text_choices)
+
+ app.at(SubmitText, text=selected_text)
+ result = app.at(ValidateResult, expected_text=selected_text).result
+
+ # Use the custom assertion to validate that the result is True
+ app.asserts(IsTrue, result)
+
+ # Close the application
+ app.at(setup.CloseApp)
+```
+
+# Conclusion
+The code needs some polishiment, like the `title` variable is never used, the occurrences of `app.asserts` can be suppressed to a single line like `app.at(OpenSamplePage, file_path=str(file_path)).asserts(it.IsEqualTo, "Sample page")` and the transaction `ValidateResult` does not seems to be a valid transaction the user does in the application, so this code could be merged with `SubmitText` to reduce the verbosity. However, despite these embellishments, the code works perfectly.
\ No newline at end of file
diff --git a/docs/images/copy_raw_content.png b/docs/images/copy_raw_content.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec5ed7976c4cd3cfaf503a9120c21d268cb32613
GIT binary patch
literal 2222
zcmV;f2vPTmP)ZgXgFbngSdJ^%m!CUiwubVG7w
zVRUJ4ZXi@?ZDjy-WpW@gF(7JTZXhx+GBqGEH99afIyEy08#@dD00<6AL_t(&f$f{y
zQybSE$3JJat6f+mtw0D6k}qIW91MwL;-qzFI&Np$X{UMUOkdlV_N{&HpVB`f)0sYW
znzU&fx4AfJY{!j>Z5+%+;vx`2Na()W-O~qR>;z;C$i{Z~yy=|Z{Lb0^?)jbHxkz;P
z4@!=Brcn;N14rzRx|@L&4r7`onx+BbDRk-Ehx9z^woR#2q*N?!t8Xfj-|r{V9Ys+T
z?1oLXS_7V)$msX`G0hIv*K<#$J2mrJ;=i|2VQ@7zHULTvV_Ugw9ULQCVe2fWu2!WIuAhm;)!Rf8LJ+bnd_I+8A&=`gt?yI|Ia{mn$Hh6`4p=So`Lkl4
zUmicrXKiN7d_EE>@ud9f0}vR7K}R6475a7@Nx@)r0snXn@ZMewWr<3BLL6ya2*G~-@jG1J>2n7ynjJZ6oq5Q
zj&thtY1$0Esa>sBXJ%#^F@NtV_U;=BM8x&^O7a;glhR6uH0Rr`iv~C6d@>=D`d0TrZ(krsbvBI7=}(L6v8l!1Nk{%
zxRVrTuX6d_tIVv~plDcUO@@CrNTNH0Ia8MANA}M*RNb-K^tNu2CGvy
z_}_Yz@$bLJpfAPs5B|$S#|W$#;_xC`mN-P#5
zkvN8?Y2@?k+`fIgwU7A-g25n)qEIT8xAn_3O{A1;Y&4%2cjAB|DRjN^Cco~yK)&iB
zd^%RFkH+1%nVQd1R>Dl*_<+pJJ!W$T=R?C>>W*T}q*;>@%=E}!EEc1`zn|IJIaGD?^{T3mcs$O)zyQn3
z%X|K44jk^>T9Nf!jS~6Y6B8^hE$-Q8OXno%*2pd{Q&MBJ>+Qr(
zkI<*w;$N5l%EwCqUXOg2(*Xs6&B{Us=iJMj=q@wy$yFv9CH}pW#2ZQOUP)7FaaQPg
z9-&Z>cs$OAw_iOpjGS!8~p*wRB0&+|wnlSF%Z2n2#n?F_>ponAujakph)U;tg$sZ=V|YBe0k
zA(cuYrKB&`$I9{w$z%%0*$fY+X>xjK2&8<{B6%!-_BY;h7Ae#o%pWrUB_I7qLtW01
zOkW1hF&yhams!62_+3`J+o={MY|Qcbhi_93RI~$~5o)`N8D4`j1(zHSv)B=p81veogV|->3H9okt+0B)zmmdTD8^%(5(^
z(J0f?)3~m?&3QVyl^5(DM3mCfexb4o~CxuXcV*EY{@pz?CdO_
z=MnDg#HaXnwf8)i-1<7nWb(26zi=Wsjzcz^B^K)^7z{PF3j{h?UQV~Dl$67{l6ZeH{_rwj`$&?KTCIv9@3aYA6e<<_YG29gj1uQF!ZP%ZRaBx^JwABb9zRLW36!Pm^<=e)zl#-1?0mo@z
znjL7GALWpS{
Date: Tue, 21 Jan 2025 16:43:11 -0300
Subject: [PATCH 3/3] Reverting the changed to fix teh intermitent issue
examples/unit_test/async_todo_list/test_async_todo.py::TestAsyncToDo::test_async_list_tasks_many_assertions
/home/runner/work/guara/guara/guara/asynchronous/transaction.py:126: RuntimeWarning: coroutine 'IAssertion.validates' was never awaited
self._coroutines.clear()
Enable tracemalloc to get traceback where the object was allocated.
---
guara/asynchronous/transaction.py | 54 +++++++------------------------
1 file changed, 12 insertions(+), 42 deletions(-)
diff --git a/guara/asynchronous/transaction.py b/guara/asynchronous/transaction.py
index ba91b93..c091460 100644
--- a/guara/asynchronous/transaction.py
+++ b/guara/asynchronous/transaction.py
@@ -117,47 +117,17 @@ async def perform(self) -> "Application":
Returns:
(Application)
"""
- for index in range(0, len(self._coroutines), 1):
- (
- await self.get_assertion(index)
- if not await self.get_transaction(index)
- else None
- )
+ for coroutine in self._coroutines:
+ if coroutine.get(self._TRANSACTION):
+ LOGGER.info(f"Transaction '{self._transaction_name}'")
+ for k, v in self._kwargs.items():
+ LOGGER.info(f" {k}: {v}")
+ self._result = await coroutine.get(self._TRANSACTION)
+ continue
+
+ LOGGER.info(f"Assertion '{self._it.__name__}'")
+ LOGGER.info(f" actual: '{self._result}'")
+ LOGGER.info(f" expected: '{self._expected}'")
+ await coroutine.get(self._ASSERTION)
self._coroutines.clear()
return self
-
- async def get_transaction(self, index: int) -> Coroutine[None, None, bool]:
- """
- Retrieving the transaction from the coroutine.
-
- Args:
- index: (int): The index of the current coroutine.
-
- Returns:
- (Coroutine[None, None, bool])
- """
- transaction: Coroutine[None, None, Any] = self._coroutines[index].get(
- self._TRANSACTION
- )
- if transaction:
- LOGGER.info(f"Transaction: {self._transaction_name}")
- for key, value in self._kwargs.items():
- LOGGER.info(f" {key}: {value}")
- self._result = await transaction
- return True
- return False
-
- async def get_assertion(self, index: int) -> Coroutine[None, None, None]:
- """
- Retrieving the assertion from the coroutine.
-
- Args:
- index: (int): The index of the current coroutine.
-
- Returns:
- (Coroutine[None, None, None])
- """
- LOGGER.info(f"Assertion: {self._it.__name__}")
- LOGGER.info(f" Actual : {self._result}")
- LOGGER.info(f" Expected: {self._expected}")
- return self._coroutines[index].get(self._ASSERTION)